"未经审视的生活不值得过"------ 苏格拉底
有这样一个开发场景:
封装一个自定义组件,接收父级的props传进来做value,同时自己也是受控组件
给几分钟时间思考下大概怎么写。
起手就写
看到大部分人都是这样写
jsx
const Com = ({
value
}) => {
const [selfValue, setSelfValue] = useState(value);
useEffect(() => {
setSelfValue(value);
}, [value]);
return <div>
<input
value={selfValue}
onChange = {e => {setSelfValue(e.target.value);}}
/>
</div>
}
在这个例子中,每次 value
props
变化时,useEffect
都会触发,导致 selfValue
被更新。虽然功能上可以实现,但这种方式会增加不必要的渲染开销,尤其是在复杂组件中,频繁的 useEffect
触发可能会导致性能问题。
相信各位Reacter都会使用useEffect来处理这种情况【对标vue的watch?】,即通过"监听"父组件传进来的值来更新到子组件内部的值
然而react是没有监听这个概念的,它,叫同步副作用。
关于useEffect滥用
关于这个讨论,网上的文章其实也有很多,大家可以自行查阅。其实滥用useEffect除了造成排查问题更加困难,还有一个更加直观的问题,就是会多一次渲染。
毕竟useEffect是需要render后才会执行回调函数,当父组件更新了value,子组件先render一次,useEffect再set一次,render第二次。
自定义hook
于是乎问了下各个ai,发现全部都是使用useEffect,呵呵,还是不够智能。还是自己亲手下场。
设计思路
-
先设想希望用户怎么用,大概的模样
const [count, setCount] = useStateFromProps(props.count)
-
props.count跟count同步,不使用useEffect来同步,那就直接return props.count?当然不行,因为count还得受控,那就使用useRef【curValue】做中间商,注意:更改时强行update一次,不然视图不会更新
-
setValue得模仿useState返回的的第二个参数,即支持入参是函数,同时更新一下useRef的值,这样子组件就可以接收父组件做初始值,同时也自己能受控了
-
父组件更新时,子组件的值也得更新,那就弄多一个useRef【oldValue】存储旧值,每次render将【oldValue】和【curValue】做比较,不同的话证明props.count变了,【curValue】得变成props.count
tsx
import { useState, useRef, useMemo } from 'react';
const useStateFromProps = <T>(props: T) => {
const [, forceUpdate] = useState({});
const oldValue = useRef<T>(props);
const curValue = useRef<T>(props);
if (oldValue.current !== props) {
oldValue.current = props;
curValue.current = props;
}
return useMemo(
() =>
[
curValue.current,
(prevState: T) => {
const updatedValue =
typeof prevState === 'function' ? prevState(curValue.current) : prevState;
curValue.current = updatedValue;
forceUpdate({});
},
] as [T, React.Dispatch<React.SetStateAction<T>>],
[props, curValue.current],
);
};
export default useStateFromProps;
这段代码的核心逻辑是:它通过 useRef
来存储旧的 props
和当前的值,当 props
发生变化时,更新 curValue
。同时,返回一个类似 useState
的数组,第一个元素是当前状态值,第二个元素是用于更新状态的函数。
使用
tsx
import { useState } from 'react';
import { useStateFromProps } from '@wjjhhh/jhooks';
const Child = ({ num: parentNum }: { num: number }) => {
const [num, setNum] = useStateFromProps(parentNum);
console.count('child render');
return (
<div>
<div>
child state: {num}
<button onClick={() => setNum(num + 1)}>add</button>
</div>
</div>
);
};
export default () => {
const [num, setNum] = useState(0);
console.count('parent render');
return (
<div>
<div>
parent state: {num} <button onClick={() => setNum(num + 1)}>add</button>
</div>
<Child num={num} />
</div>
);
};
结语
第一次掘金发文,很多格式和工具都不会用,多多包涵。同时请大家支持小弟封装的react hooks库,专做一些稀奇古怪功能的hooks库