简单的 useState 实现

本文写于 2020 年 10 月 21 日

以下是一段非常简单的 React 代码:

const App = () => {
const [n, setN] = useState(0);
return (
<div>
{n}
<button onClick={() => setN(x => x + 1)}>+1</button>
</div>
);
} React.render(<App />, rootElement)

这样的用法和以往的 setState 是有明显的不同的,他看起来更像 redux——我们初始化一个 state,然后 dispatch 一个 action,再由 reducer 改变 state 后返回新的 state

Redux 思想实现 useState

既然我们觉得它像,那我们就来自己实现一个吧。

不熟悉 Redux 思想的同学请自行阅读文档

const useState = (initialValue) => {
let state = initialValue;
const dispatch = (newState) => {
state = newState;
render(<App />, document.getElementById('root'));
};
return [state, dispatch];
};

然后我们用这个自定义的 useState 代替 React 的 useState——就会发现我们失败了,setN 无论如何都不会有任何反应。

这是因为我们每次重新 render 的时候都重新执行了函数,于是我们总是会重新赋值

为什么不会重新赋值?

对于这段 React 代码来说,当我们第一次运行时,React 会进行首次渲染,即 render(<App />, ...)

在此过程中,会先调用 App(),之后便会得到虚拟 DOM,再创建真实的 Div。

当我们触发点击事件时,会调用 setN,再次 render()。之后调用 App(),然后得到新的虚拟 DOM,进行 diff 算法,根据 diff 算法的结果去更新新的 Div。

而不管是第一次渲染,还是第二次调用,都会调用 useState()

但是我们写的是 useState(0) 啊,两次调用明明是一样的代码,为何 n 的值不同?怎么解决这个问题呢?

很简单,闭包嘛。

const createUseState = () => {
let state;
const useState = (initialValue) => {
if (!state) {
state = initialValue;
}
const dispatch = (newState) => {
state = newState;
render(<App />, document.getElementById('root'));
};
return [state, dispatch];
};
};

这样就解决了重新赋值的问题。

多次调用

但是我们需要多次调用 useState 呀,不可能只用一次的。

于是我们将 state 改为一个数组:const state = [];

const createUseState = () => {
const state = [];
let index = 0;
return (initialValue) => {
state[index] = state[index] || initialValue;
const currentIndex = index;
const dispatch = (newState) => {
state[currentIndex] = newState;
// 重点
index = 0;
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
};
return [state[index++], dispatch];
};
};

我们创建了一个 index 变量来控制索引。它需要我们保证每次重新渲染 App 传入数组的元素是一样的——这就是为什么我们不可以将 useState 写在 if 判断中。

在上述代码中有一处重点,在于我们需要在每次 set 之后将索引归零 index = 0

因为每次 render 结束后,React 都会重新执行该函数。

(完)

简单的 useState 实现的更多相关文章

  1. Hooks中的useState

    Hooks中的useState React的数据是自顶向下单向流动的,即从父组件到子组件中,组件的数据存储在props和state中,实际上在任何应用中,数据都是必不可少的,我们需要直接的改变页面上一 ...

  2. 手写useState与useEffect

    手写useState与useEffect useState与useEffect是驱动React hooks运行的基础,useState用于管理状态,useEffect用以处理副作用,通过手写简单的us ...

  3. useContext 让父子组件传值更简单(五)

    有了useState和useEffect已经可以实现大部分的业务逻辑了,但是React Hooks中还是有很多好用的Hooks函数的,比如useContext和useReducer. 在用类声明组件时 ...

  4. useState 的介绍和多状态声明(二)

    useState的介绍 useState是react自带的一个hook函数,它的作用是用来声明状态变量. 那我们从三个方面来看useState的用法,分别是声明.读取.使用(修改).这三个方面掌握了, ...

  5. React Hooks简单业务场景实战(非源码解读)

    前言 React Hooks 是React 16.7.0-alpha 版本推出的新特性.从 16.8.0 开始,React更稳定的支持了这一新特性. 它可以让你在不编写 class 的情况下使用 st ...

  6. 前端开发:mock.js的简单应用(生成随机数据,拦截 Ajax 请求)

    摘要 在前端开发过程中,后端接口还没有完全开发完成时,前端开发人员就需要学会自己模拟后端接口数据,更快更好的完成开发任务.模拟后端接口数据的js库有很多,今天就简单就简单的分享下mock.js在前端开 ...

  7. 认清 React 的useState逻辑

    useState运行过程解析 function App() { const [n, setN] = useState(0); //使用 myUseState() return ( <div> ...

  8. React简单教程-6-单元测试

    前言 我想大部分人的前端测试,都是运行项目,直接在浏览器上操作,看看功能正不正常.虽然明明有测试库可以使用,但是因为"要快"的原因,让好好做测试变成了一件影响效率的事. 因为这种无 ...

  9. React简单教程-5-使用mock

    前言 一个前后端分离的项目,前端人员需要对接后端的接口.如果在后端的接口没有开发好,或者没有测试版可以对接的情况下,前端人员也不能坐等后端接口写好后再开始开发. 一个项目的,理想情况下接口的规范应该是 ...

随机推荐

  1. PCB中的生产工艺、USB布线、特殊部件、蓝牙天线设计

    PCB中的生产工艺.USB布线.特殊部件.蓝牙天线设计 (2016-07-20 11:43:27) 转载▼     PCB生产中Mark点设计 1.pcb必须在板长边对角线上有一对应整板定位的Mark ...

  2. (SSM框架)实现小程序图片上传(配小程序源码)

    阅读本文约"2分钟" 又是一个开源小组件啦! 因为刚好做到这个小功能,所以就整理了一下,针对微信小程序的图片(文件)上传! 原业务是针对用户反馈的图片上传.(没错,本次还提供小程序 ...

  3. js随手笔记-------理解JavaScript碰撞检测算法核心简单实现原理

    碰撞检测在前端游戏,设计拖拽的实用业务等领域的应用场景非常广泛,今天我们就在这里对于前端JavaScript如何实现碰撞检测算法进行一个原理上的探讨,让大家能够明白如何实现碰撞以及碰撞的理念是什么:1 ...

  4. h5 在全屏iphonex中的适配

    iphonex 已经上线有一段时间了,作为业界刘海屏幕第一款机型,导致全屏不能正常的全屏显示了,,所以需要对iphonx 适配,下面就详细说说如何适配 先看一张适配前后的图: iphonex 提供的 ...

  5. Zookeeper中的Leader选取机制

    一.Zookeeper是什么? ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应 ...

  6. 6.S081-2021-Lab3 Pgtbl学习笔记

    Speed up system calls 根据hints查看kernel/proc.c中的函数proc_pagetable // kernel/proc.c // Create a user pag ...

  7. c++对c的拓展_函数的引用

    注意:1.引用可作为形参较指针更为直观 2可作为返回值 (判断标准:空间是否释放) #include <iostream> #include <stdlib.h> using ...

  8. MySQL---什么是事务

    什么是事务 一个数据库事务通常包含对数据库进行读或写的一个操作序列.它的存在包含有以下两个目的: 为数据库操作提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方 ...

  9. python---单链表的常用操作

    class Node(object): """结点""" def __init__(self, data): self.data = dat ...

  10. springboot+mybatis实现数据分页(三种方式)

    项目准备 1.创建用户表 2.使用spring初始化向导快速创建项目,勾选mybatis,web,jdbc,driver 添加lombok插件 <?xml version="1.0&q ...