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库

求点击进入点⭐

相关推荐
薛定谔的算法2 小时前
# 从0到1构建React项目:一个仓库展示应用的架构实践
前端·react.js
Tina学编程3 小时前
HTML基础P1 | HTML基本元素
服务器·前端·html
一只小风华~4 小时前
Web前端:JavaScript和CSS实现的基础登录验证功能
前端
90后的晨仔4 小时前
Vue Router 入门指南:从零开始实现前端路由管理
前端·vue.js
LotteChar4 小时前
WebStorm vs VSCode:前端圈的「豆腐脑甜咸之争」
前端·vscode·webstorm
90后的晨仔4 小时前
零基础快速搭建 Vue 3 开发环境(附官方推荐方法)
前端·vue.js
洛_尘4 小时前
Java EE进阶2:前端 HTML+CSS+JavaScript
java·前端·java-ee
孤独的根号_5 小时前
Vite背后的技术原理🚀:为什么选择Vite作为你的前端构建工具💥
前端·vue.js·vite
吹牛不交税5 小时前
Axure RP Extension for Chrome插件安装使用
前端·chrome·axure
薛定谔的算法5 小时前
# 前端路由进化史:从白屏到丝滑体验的技术突围
前端·react.js·前端框架