【React】将值从子组件传递给父组件…总结(对于函数组件的情况)

在React中,可以使用props将函数传递给子组件。

然而,问题是,当子组件内存在参数,并且希望将其传递给父组件时,应该如何解决呢?尽管在父组件中从子组件中传递值是可能的,但与Vue或Angular不同,React似乎没有类似于子组件向父组件传递值的emit方法的存在。

此外,在之前的React技术网站上,我经常看到有关从子组件传递值到父组件是否不可能或者不必要的说明。但是,当我继续查看国外网站时,我发现可以通过使用以下的回调函数的方法来实现调用子组件设置的值,并进行了验证测试,现将其总结如下。

由于使用基于React16.8之后的Hooks的函数组件编写变得更加主流,因此在修订版文章中我将不再解释类组件(我会将旧文章中的类组件专用内容改写)。

如果只从子组件传递事件的话:

如果只有事件方法存在于子组件中,则使用回调函数是可以的。

将事件处理传递

以下是原始网站,我们将使用组件创建一个简单的计算器工具。

    Reactコンポーネント間の値の受け渡し

使用这个作为基础来制作计算器的按键。

当键入此键时,将显示字符并进行以下分类。

    • 文字が数字の場合は、数値に変換する。引き続き数字の場合は桁をずらし、計算記号が打たれた場合は被序数を決定。

 

    • 文字が計算記号の場合は、被序数と序数を確定し、計算を行う

 

    • 文字が=の場合は合計を計算する。

 

    文字がCの場合は全てリセットする。

这是通过以上方法创建的示例程序。

    親コンポーネント
import React, { useState, useEffect } from "react";
import Setkey from "./Setkey";

const Calc = ()=>{
	const[ d_lnum, setLnum ] = useState(null)
	const[ d_cnum, setCnum ] = useState(0)
	const[ d_sum, setSum ] = useState(0)
	const[ d_str, setStr ] = useState("")
	const[ d_sign, setSign ] = useState("")
	const vals = [
			[['7','7'],['8','8'],['9','9'],['div','÷']],
			[['4','4'],['5','5'],['6','6'],['mul','×']],
			[['1','1'],['2','2'],['3','3'],['sub','-']],
			[['0','0'],['eq','='],['c','C'],['add','+']],
	]
	
	const getChar = (chr,strtmp)=>{
		let lnum = d_lnum //被序数
		let cnum = d_cnum //序数
		let sum = d_sum //合計
		let sign = d_sign
		let str = d_str + strtmp
		//console.log(lnum,cnum,sum,str,sign)

		if(chr.match(/[0-9]/g)!== null){
			let num = parseInt(chr)
			cnum = cnum * 10 + num //数値が打ち込まれるごとに桁をずらしていく
		}else if(chr.match(/(c|eq)/g) == null){
			if(lnum != null){
				lnum = calc(sign,lnum,cnum)
			}else{
				if(chr == "sub"){
					lnum = 0
				}
				lnum = cnum
			}
			sign = chr
			cnum = 0
		}else if( chr == "eq"){
			lnum = calc(sign,lnum,cnum)
			sum = lnum
		}else{
			lnum = 0
			cnum = 0
			sum = 0
			str = ''
		}
		setLnum(lnum)
		setCnum(cnum)
		setSum(sum)
		setStr(str)
		setSign(sign)
	}
		
	//計算処理
	const calc = (mode,lnum,cnum)=>{
		switch(mode){
			case "add": lnum = cnum + lnum
			break;
			case "sub": lnum = lnum - cnum
			break;
			case "mul": lnum = lnum * cnum
			break;
			case "div": lnum = lnum / cnum
			break;
		}
		return lnum
	}
	
	return (
			<>
			{
				vals.map((val,key)=>{
					return (
						<div key={key}>
						{
							val.map((v,i)=>{
								return(<Setkey key={i} val={v[1]} getChar={()=>{getChar(v[0],v[1])} }/>)
							})
						}
						</div>
					)
				})
			}
			<div>
				<p>打ち込んだ文字:{d_str}</p>
				<p>合計:{d_sum}</p>
			</div>
			</>
	)
}

export default Calc
    子コンポーネント
import React from "react";
const Setkey = (props)=>{
	const { getChar,val } = props
	return (
		<button className="square" onClick={()=>{getChar()}}>
			{val}
		</button>
	)
}

export default Setkey

这个功能最低限度上可以作为一个计算器使用,但接下来我们要进入正题。点击事件和由此触发的getChar方法存在于子组件生成的JSX内,但子组件中没有提供值。

然而,由于可以使用回调函数来传递事件处理,因此在getChar方法被触发时,父组件的同步方法getChar中的参数v[0]和v[1]将被传递给getChar方法,从而进行计算处理。

    return(<PushKey val={v[1]} getChar={()=>{getChar(v[0],v[1])} }/>)

在这里需要注意的是,父组件中的表达式左侧和表达式右侧的”getChar”是不同的,表达式左侧是用于同步的回调函数。

因此,敢试着将其区分开来,现在,在国外网站中,为了更容易区分,通常将方法部分称为”hoge”,回调函数部分称为”hogeState”。

    {/*左側はコールバック関数、右側は処理用メソッド*/}
    return(<PushKey val={v[1]} getCharState={()=>{getChar(v[0],v[1])} }/>)
        {/*子コンポーネントに置かれたコールバック関数*/}
		<button className="square" onClick={()=>{getCharState()}}>

如果要从子组件传递事件和值,用中文原生方式解释的话, 可以说是:“当需要从子组件传递事件和值时”。

根据第1章的内容,我们将进入关于如何将子组件的值传递给父组件的处理过程的说明。首先,我们使用事件触发来传递值,这样我们可以传递任意的键或索引。

这次我们将使用Typescript来解释tsx文件,但基本部分没有变化。

将子组件的值调用到父组件中

这个程序是将使用Typescript构建的简洁的ToDo系统(源代码是Vue3)改造成React,以便可以切换状态。在更改状态时,它会调用子组件上设置的索引。

import React,{ useState } from 'react';
import Todo from './components/Todo'
import AddTodo from './components/AddTodo'
import './Styles.css'
//可読性を上げるためにmodelの導入
import { ToDo } from './todo.model'

const App: React.VFC =()=>{
	const [todos, setTodos] = useState<ToDo[]>([]);
	const [title,setValue] = useState(null);
	const todoAdd = (text: string,state: string)=>{
		setTodos(prevTodos => [
			...prevTodos,
			{id: Math.random().toString(), text: text,state: "未作業"}
		])
	}
	const toggle = (i: number,slist: any)=>{
			const stat = {...slist[i]}
            //ステータス切り替え
			stat.state = (stat.state === '未作業') ? '作業中' : '作業完了';
			slist[i] = stat
			setTodos([...slist])
	}
	
	const deleteList = (i: number)=>{
		setTodos(todos.filter((_,idx)=> idx !== i))
	}

  return (
    <div className="nav">
		<h1>React-typescript-TODO テスト React18</h1>
			{ todos.length }件を表示中
			<table>
				<thead>
					<tr>
						<th className="id">ID</th>
						<th className="comment">コメント</th>
						<th className="state">状態</th>
						<th className="button">-</th>
					</tr>
				</thead>
				<tbody>
					<Todo 
						items={todos}
						toggleState={(index:any)=>toggle(index,todos)}
						delState={(index:any)=>deleteList(index)}
					/>
				</tbody>
			</table>
			<h2>新しい作業の追加</h2>
			<AddTodo todoAdded={todoAdd} />{/*フォーム制御*/}
    </div>
  );
}

export default App;

//オブジェクトのインターフェース設定
interface TodoProps{
	items: {
		id: string,
		text: string,
		state: any
	}[],
	toggleState: any,
}

const Todo: React.FC<TodoProps> = props =>{
	return(
		<>
			{props.items.map((list,idx) =>(
				<tr key={idx}>
				<td>{list.id}</td>
				<td>{list.text}</td>
					<td className="state">
						<button onClick={()=>props.toggleState(idx)}>
							{list.state}
						</button>
					</td>
					<td className="button">
						<button onClick={()=>props.delState(idx)}>
						削除
						</button>
					</td>
				</tr>
			))}
		</>
	)
}

export default Todo;

使用回调函数,从内联函数的参数中传递值。

本题涉及以下部分。toggleState方法的参数是通过map方法传递给数组的索引,并将该索引作为参数传递给位于Todo.tsx文件中的回调函数toggleState的内联函数。然后,将该参数分配给内联函数内的toggle方法的参数,通过此方法在方法内进行处理,并进行同步操作流程。

		<>
			{props.items.map((list,idx) =>(
				<tr key={idx}>
				<td>{list.id}</td>
				<td>{list.text}</td>
					<td className="state">
                        {/*引数idxは選択したリストのインデックスが格納される*/}
						<button onClick={()=>props.toggleState(idx)}>
							{list.state}
						</button>
					</td>
					<td className="button">
						<button>
						削除
						</button>
					</td>
				</tr>
			))}
		</>
	const toggle = (i: number,slist: any)=>{
			const stat = {...slist[i]} //修正したいリストを抽出
			stat.state = (stat.state === '未作業') ? '作業中' : '作業完了'; //ステータス切り替え
			slist[i] = stat //代入
			setTodos([...slist]) //フックで反映、分割代入にしないと反映されない。
	}

    {/*中略*/}
    {/*引数idxが子コンポーネントから呼び出した対象リストのインデックス*/}
	<Todo items={todos} toggleState={(idx:any)=>toggle(idx,todos)} />

以这种方式,您可以将子组件的值传递给父组件并进行同步。对于删除操作,只需要获取索引即可。

react.jpg

我們從子組件中呼叫了陣列Todos的索引來改變所選列表的狀態。

如果只需从子组件传递值,那么…

如果在不触发事件的情况下,只想将子组件的值传递给父组件,可以通过在子组件中使用useEffect钩子来实现。在这个钩子内部创建一个用于传递的回调函数,然后像以前一样将值和参数传递给父组件。然后,在父组件中使用useState钩子进行同步(直接在内联函数中使用同步方法setHoge效率更高)。

我尝试改造了先前的Todo工具标题,使其从子组件传递。

import React,{ useState } from 'react';
import Todo from './components/Todo'
import AddTodo from './components/AddTodo'
import './Styles.css'
//可読性を上げるためにmodelの導入
import { ToDo } from './todo.model'

const App: React.VFC =()=>{
  {/*中略*/}
  const [title,setTitle] = useState(null);
  {/*中略*/}
  return (
    <div className="nav">
        {/*子コンポーネントに設定したタイトルが表示される*/}
		<h1>{title}</h1>
			{/*中略*/}
        {/*setTitleStateの*/}
		<Todo 
			items={todos}
			toggleState={(index:any)=>toggle(index,todos)}
			delState={(index:any)=>deleteList(index)}
			setTitleState={(val:any)=>setTitle(val)} 
		/>

  );
}

export default App;
import React,{ useEffect } from 'react';
//オブジェクトのインターフェース設定
interface TodoProps{
	items: {
		id: string,
		text: string,
		state: any
	}[],
	toggleState: any,
	setTitleState:any, //コールバック関数を設定しておく
	delState:any,
}

const Todo: React.FC<TodoProps> = props =>{
	useEffect(()=>{
	const title = 'React-typescript-TODO テスト React18' //親コンポーネントに反映させるタイトル
	props.setTitleState(title) //受け渡し用のコールバック関数
},[props])
	return(
		<>
			{props.items.map((list,idx) =>(
				<tr key={idx} >
				<td>{list.id}</td>
				<td>{list.text}</td>
					<td className="state">
						<button onClick={()=>props.toggleState(idx)}>
							{list.state}
						</button>
					</td>
					<td className="button">
						<button onClick={()=>props.delState(idx)}>
						削除
						</button>
					</td>
				</tr>
			))}
		</>
	)
}

export default Todo;

当子组件需要将事件和值传递给父组件,并再次反映到子组件上的情况下

虽然有点难理解,但是这种情况下是指子组件→父组件→子组件。 子组件中已经准备了相同的变量。

父母必须向子女传递价值观,所以我会准备好将datastate={data}传递给他们。

import React,{useState} from 'react';
import Test2 from './Test2';
const Test = ()=>{
  const [data,setData] = useState(null)
  function bind(idx){
    const add = idx * 2
    setData(add)
  }
  return(
    <>
      <Test2 fromChildState={(idx)=>(bind(idx))} datastate={data} />
    </>
  )
}
export default Test

对于子组件来说,需要与传递的props.datastate进行同步,因此我们在这里使用setData2和useState来进行控制。然后,使用useEffect钩子,只有当props.datastate不为null时,才进行同步操作,这样子组件就可以显示初始值和通过父组件同步处理的值(还可以使用将父组件的setData传递给props的方法)。

import React,{useState,useRef,useEffect} from 'react'
const Test2 = (props)=>{
  const {datastate,fromChildState} = props
  const ini = 2
  const [data2,setData2 ] = useState(ini)
  useEffect(()=>{
    if(props.datastate != null){
      setData2(props.datastate)
    }
  },[props])
  return(
    <>
      <button onClick={()=>fromChildState(data2)}>てすと</button>
      { data2 }
    </>
  )
}
export default Test2

使用类组件的情况下

由于我们将旧文章整理成了专门用于课程组件的汇总网站,请参考该网站。

【React】从子组件传值给父组件的整理(类组件场景)

Note: Please note that due to the limitations of the system, the Chinese translation here might not include the specific characters used in the original Japanese text. However, the meaning and context have been preserved in the translation.