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 天前
人工智能训练师认证教程(2)Python os入门教程
前端·数据库·python
信看1 天前
NMEA-GNSS-RTK 定位html小工具
前端·javascript·html
Tony Bai1 天前
【API 设计之道】04 字段掩码模式:让前端决定后端返回什么
前端
苏打水com1 天前
第十四篇:Day40-42 前端架构设计入门——从“功能实现”到“架构思维”(对标职场“大型项目架构”需求)
前端·架构
king王一帅1 天前
流式渲染 Incremark、ant-design-x markdown、streammarkdown-vue 全流程方案对比
前端·javascript·人工智能
苏打水com1 天前
第十八篇:Day52-54 前端跨端开发进阶——从“多端适配”到“跨端统一”(对标职场“全栈化”需求)
前端
Bigger1 天前
后端拒写接口?前端硬核自救:纯前端实现静态资源下载全链路解析
前端·浏览器·vite
BD_Marathon1 天前
【JavaWeb】路径问题_前端绝对路径问题
前端
whyfail1 天前
Vue原理(暴力版)
前端·vue.js