React受控组件如何优雅接收父组件更新

"未经审视的生活不值得过"------ 苏格拉底

有这样一个开发场景:

封装一个自定义组件,接收父级的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,呵呵,还是不够智能。还是自己亲手下场。

设计思路

  1. 先设想希望用户怎么用,大概的模样

    const count, setCount = useStateFromProps(props.count)

  2. props.count跟count同步,不使用useEffect来同步,那就直接return props.count?当然不行,因为count还得受控,那就使用useRef【curValue】做中间商,注意:更改时强行update一次,不然视图不会更新

  3. setValue得模仿useState返回的的第二个参数,即支持入参是函数,同时更新一下useRef的值,这样子组件就可以接收父组件做初始值,同时也自己能受控了

  4. 父组件更新时,子组件的值也得更新,那就弄多一个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库

求点击进入点⭐

相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95271 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端