父子组件传值基本能占到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])
}
- 动态绑定,setCount导致组件更新
- 动态绑定,setCountObj导致组件更新
- countObjOrigin绑定了一个字面量对象,组件每次更新每次都会被useEffect捕获(如果监听key)。countObjOrigin.count 可以更新数据
- countRef.current更新不会让组件更新,但是如果其他setState函数触发,会带着countObjRef一起更新视图; countObjRef绑定了一个字面量对象,每次父组件更新(需要其他条件触发),子组件接收到的一个新的对象;useEffect 依赖项绑定countObjRef.count,如果父组件更新,并且countRef.current也变化,也可以监听到数据变化(数字值的对比)
- countRef.current无法使得countRefCurrent更新组件,当其他setState函数触发组件更新,并且countRef.current也变化,countRefCurrent会被子组件当成一个数字更新。countRef.current不改变,countRefCurrent无论如何不会触发useEffect。
- 当你需要在某些时候需要组件更新,可以调用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,因为第一次数据流还没有更新到子组件