Bootstrap

React hooks父子组件之间通讯

父子组件传值基本能占到60%以上,因为数据流向清楚,方便排查;context、redux虽然能方便使用,但是无法确定那个位置修改了页面数据,存在隐患;

一、Props

1.1 属性传值

// 父组件
export default function Parent() {
	const [info] = useState({
		id: 1,
		label: 'time'
	})
	return (<div>
		Parent content
		<Child formObj age="100" { ...info } /> // ...info 属性集中投放
	</div>)
}
// 子组件
export default function Child(props) {
	props.name === undefined
	props.formObj === true
	props.age === 100
}
// props也可以使用解构 
// function Child({ formObj, age, ...rest  }) { }
// rest可以获取剩余参数的对象: { [key]: value }, 如果全部解构定义,rest === {}

1.2 state传值

// 父组件
export default function Parent() {
	const [countObj, setCountObj] = useState({ count: 1 })
	const [count, setCount] = useState(1)
	const countRef = useRef(1)
	return (<div>
		Parent content
		<Child 
			count={count} // 1
			countObj={countObj}  // 2
			countObjOrigin={{ count: count }}  // 3
			countObjRef={{ count: countRef.current }}   // 4
			countRefCurrent={countRef.current} // 5
		/>
	</div>)
}

// 子组件
export default function Child() {
	useEffect(() => {
	  // 监听
	}, [key])
}

  1. 动态绑定,setCount导致组件更新
  2. 动态绑定,setCountObj导致组件更新
  3. countObjOrigin绑定了一个字面量对象,组件每次更新每次都会被useEffect捕获(如果监听key)。countObjOrigin.count 可以更新数据
  4. countRef.current更新不会让组件更新,但是如果其他setState函数触发,会带着countObjRef一起更新视图; countObjRef绑定了一个字面量对象,每次父组件更新(需要其他条件触发),子组件接收到的一个新的对象;useEffect 依赖项绑定countObjRef.count,如果父组件更新,并且countRef.current也变化,也可以监听到数据变化(数字值的对比)
  5. countRef.current无法使得countRefCurrent更新组件,当其他setState函数触发组件更新,并且countRef.current也变化,countRefCurrent会被子组件当成一个数字更新。countRef.current不改变,countRefCurrent无论如何不会触发useEffect。
  6. 当你需要在某些时候需要组件更新,可以调用setState函数,帮助那些不能更新组件的变量更新组件

1.3 子组件独立props

// 父组件
export default function Parent() {
	const [count, setCount] = useState(1)
	return (<div>
		Parent content
		<Child countP={count} />
	</div>)
}
// 子组件
export default function Child({ countP }) {
	// 子组件初始化的时候,count被定义为父组件count值,往后他们将各自独立维护
	const [count, setCount] = useState(countP)
	// 也可以选择继续同步父组件count值;在父组件count不经常更新的情况下,子组件独立维护count值
	// 父组件count值一旦更新,子组件count会被覆盖
	useEffect(() => {
    	setCount(countP);
 	}, [countP])
}

二、hadel函数

2.1 原生函数

// 父组件
export default function Parent() {
	const [count, setCount] = useState(1)
	return (<div>
		Parent content
		<Child noticeParentFun={(e) => {
			alert(0);
			// 子组件修改父组件状态
			setCount(count++);
		}} />
	</div>)
}

// 子组件
export default function Child({ noticeParentFun }) {
	// 传递函数,子组件调用;如果需要使用事件对象,从onClick事件回调参数e处获取
	return (<div onClick={(e) => noticeParentFun(e)}>
		<div onClick={noticeParentFun}>
			child content
		</div>
	</div>)
}

2.2 promise函数

promise异步任务的联合,增加父子组件交互的灵活性;便于做接口loading、子组件关闭或者子组件数据重置

// 父组件
export default function Parent() {
	function delay(ms, num) {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(num)
            }, ms);
        })
    }
    const getData= async (data) => {
    	const res = await delay(data); // res = 1
    	if(res) {
    		return true
    	}
    }
	return (<div>
		Parent content
		<Child onSubmit={getData} />
	</div>)
}

// 子组件
export default function Child({ onSubmit }) {
	const [count, setCount] = useState(1)
	const clickHandel = async () => {
		const res = await onSubmit(count); // res = true | undefined
		if(res) {
			alert('end')
		}
	}
	return (<div>
		<div onClick={clickHandel}>
			child content
		</div>
	</div>)
}

三、子组件受控

// 父组件
export default function Parent() {
	const childRef = useRef(null);
	const [count, setCount] = useState(1)
	const [isShow, setIsShow] = useState(false)
	const childInit = () => {
		// 操作子组件是不受数据流影响的,拿到的是此时此刻的数据,num值由1变为2
		const num = childRef?.current?.init(0);
	}
	const getCount = () => {
		setCount(2);
		const num = childRef?.current?.getCount();
	}
	return (<div>
		Parent content
		{num}
		<Child count={count} ref={childRef} />
		// 注意这种方式需要useEffect触发isShow为true组件渲染以后,才可以让子组件受控
		// { isShow ? <Child isControl ref={childRef} /> : '' }
	</div>)
}
// 子组件 
const Child = ({ count }, ref) => { // 注意ref在props第二格位置
	const numRef = useRef(1);
	const [num, setNum] = useState(1)
	// ref在第一个参数位置 useImperativeHandle(ref, createHandle, dependencies)
	useImperativeHandle(ref, () => {
        return {
            init(data) {
            	numRef.current = data;
            	return num
            },
            getCount() {
            	return count
            },
        }
    }, [num, count]) // 如果不绑定,父组件得到的num一直是1,不再更新;
	function delay(ms, num) {
        return new Promise(resolve => {
            setTimeout(() => {
                resolve(num)
            }, ms);
        })
    }
    useEffect(() => {
    	// 模拟初始化异步数据
    	delay(2000, 2).then(res => setNum(res))
 	}, [])
	return (<div>
		child content
		{count}
	</div>)
}
export default forwardRef(Child)
  • forwardRef使得子组件受控,useImperativeHandle暴露方法给父组件
  • 注意子组件受控模式会和props数据流产生一些冲突,操作子组件是同步任务,数据流往往是异步,操作子组件的同时又调用setState,子组件先相应ref受控,再接受props数据流
  • 子组件必须渲染出来以后,才可以受控,否则childRef.current为undefined
  • useImperativeHandle(ref, createHandle, dependencies),dependencies发生变化,ref将会获得createHandle重新调用生成的返回值
  • 父组件多次触发getCount,只有第一次返回1,之后返回2,因为第一次数据流还没有更新到子组件
;