为什么 useReactive 可以直接修改 state?

大家好,我麦当。

用过 ahooks 的小伙伴会发现,里面有个神奇的 hooks 叫做 useReactive,它神奇的地方在于,打破了你对 react 的传统印象,即永远不要直接更改 state。

tsx 复制代码
import React from 'react';
import { useReactive } from 'ahooks';

export default () => {
  const state = useReactive({
    count: 0,
    inputVal: '',
    obj: {
      value: '',
    },
  });

  return (
    <div>
      <p> state.count:{state.count}</p>

      <button style={{ marginRight: 8 }} onClick={() => state.count++}>
        state.count++
      </button>
      <button onClick={() => state.count--}>state.count--</button>

      <p style={{ marginTop: 20 }}> state.inputVal: {state.inputVal}</p>
      <input onChange={(e) => (state.inputVal = e.target.value)} />

      <p style={{ marginTop: 20 }}> state.obj.value: {state.obj.value}</p>
      <input onChange={(e) => (state.obj.value = e.target.value)} />
    </div>
  );
};

那么 useReactive 是如何做到的?今天我们就来研究下它的实现原理。

Proxy 的使用

useReactive 的实现依赖于 JavaScript 的 Proxy 对象。Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。 在 useReactive 中,我们使用 Proxy 来拦截对象的 get 和 set 操作。当我们尝试获取对象的属性值时,get 操作会被触发。当我们尝试设置对象的属性值时,set 操作会被触发。

tsx 复制代码
const observer = <T extends Record<string, any>>(
  initialVal: T,
  cb: () => void
): T => {
  return new Proxy<T>(initialVal, {
    get(target, key, receiver) {
      const res = Reflect.get(target, key, receiver);
      return typeof res === "object"
        ? observer(res, cb)
        : Reflect.get(target, key);
    },
    set(target, key, val) {
      const ret = Reflect.set(target, key, val);
      cb();
      return ret;
    },
  });
};

在 get 操作中,我们首先使用 Reflect.get 获取属性值。如果属性值是一个对象,我们会递归地对这个对象进行代理。这样,我们就可以实现对嵌套对象的响应式操作。

在 set 操作中,我们首先使用 Reflect.set 设置属性值,然后调用回调函数 cb。这个回调函数的作用是通知 React 进行重新渲染

tsx 复制代码
const useReactive = <T extends Record<string, any>>(initialState: T): T => {
  const ref = useLatest<T>(initialState);
  const update = useUpdate();

  return useCreation(() => {
    // useCreation 不了解的话,先不用关注,把它当作 useMemo 即可
    return observer(ref.current, () => {
      update();
    });
  }, []);
};

当我们修改响应式对象的属性值时,Proxy 的 set 操作会被触发,然后调用 update 函数。update 函数会强制组件重新渲染,从而实现 state 的更新。

例子

它能解决 hooks 的闭包问题

tsx 复制代码
const App = () => {
    const state = useReactive({count: 0})
    useEffect(() => {  
        console.log('eff')  
        setInterval(() => {  
            state.count++;  
            console.log("=>(index.tsx:24) state.count", state.count);  
        }, 1000)  
    }, []
    
    return (
        <div>{state.count}</div>
    )
}

总结

useReactive Hook 通过使用 Proxy 对象和直接修改 state,实现了对对象的响应式操作。

ahooks.js.org/zh-CN/hooks...

相关推荐
大菠萝学姐13 小时前
基于springboot的旅游攻略网站设计与实现
前端·javascript·vue.js·spring boot·后端·spring·旅游
心随雨下13 小时前
TypeScript中extends与implements的区别
前端·javascript·typescript
摇滚侠13 小时前
Vue 项目实战《尚医通》,底部组件拆分与静态搭建,笔记05
前端·vue.js·笔记·vue
双向3313 小时前
CANN训练营实战指南:从算子分析到核函数定义的完整开发流程
前端
caleb_52013 小时前
vue cli的介绍
前端·javascript·vue.js
Swift社区13 小时前
如何监测 Vue + GeoScene 项目中浏览器内存变化并优化性能
前端·javascript·vue.js
WYiQIU13 小时前
大厂前端岗重复率极高的场景面试原题解析
前端·javascript·vue.js·react.js·面试·状态模式
IT_陈寒14 小时前
Redis 高并发实战:我从 5000QPS 优化到 5W+ 的7个核心策略
前端·人工智能·后端
vortex514 小时前
ASP vs ASP.NET vs ASP.NET Core:三代微软 Web 技术核心区别解析
前端·microsoft·asp.net
Apifox14 小时前
如何在 Apifox 中使用「模块」合理地组织接口
前端·后端·测试