1 引言

setState 是 React 框架最常用的命令,它是用来更新状态的,这也是 React 框架划时代的功能。

但是 setState 函数是 react 包导出的,他们又是如何与 react-dom react-native react-art 这些包结合的呢?

通过 how-does-setstate-know-what-to-do 这篇文章,可以解开这个秘密。

2 概述

setState 函数是在 React.Component 组件中调用的,所以最自然的联想是,更新 DOM 的逻辑在 react 包中实现。

但是 react 却可以和 react-dom react-native react-art 这些包打配合,甚至与 react-dom/server 配合在服务端运行,那可以肯定 react 包中不含有 DOM 更新逻辑。

所以可以推断,平台相关的 UI 更新逻辑分布在平台相关的包里,react 包只做了代理。

React 引擎不在 react 包里

从 react 0.14 版本之后,引擎代码就从 react 包中抽离了,react 包仅仅做通用接口抽象。

也就是说,react 包定义了标准的状态驱动模型的 API,而 react-dom react-native react-art 这些包是在各自平台的具体实现。

各平台具体的渲染引擎实现被称为 reconciler,通过这个链接可以看到 react-dom react-native react-art 这三个包的 reconciler 实现。

这说明了 react 包仅告诉你 React 拥有哪些语法,而并不关心如何实现他们,所以我们需要结合 react 包与 react-xxx 一起使用。

对于 context,react 包仅仅会做如下定义:


// A bit simplified
function createContext(defaultValue) {
let context = {
_currentValue: defaultValue,
Provider: null,
Consumer: null
};
context.Provider = {
$$typeof: Symbol.for("react.provider"),
_context: context
};
context.Consumer = {
$$typeof: Symbol.for("react.context"),
_context: context
};
return context;
}

具体用到时,由 react-domreact-native 决定用何种方式实现 MyContext.Provider 这个 API。

这也说明了,如果你不同步升级 reactreact-dom 版本的话,就可能碰到这样的报错:fail saying these types are invalid,原因是 API 定义与实现不匹配。

setState 怎么调用平台实现

每个平台对 UI 更新逻辑的实现,会封装在 updater 函数里,所以不同平台代码会为组件添加各自的 updater 实现:


// Inside React DOM
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater; // Inside React DOM Server
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater; // Inside React Native
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;

不同于 props, updater 无法被直接调用,因为这个 API 是由 react 引擎在 setState 时调用的:


// A bit simplified
setState(partialState, callback) {
// Use the `updater` field to talk back to the renderer!
this.updater.enqueueSetState(this, partialState, callback);
}

关系可以这么描述:react -> setState -> updater <- react-dom 等。

Hooks

Hooks 的原理与 setState 类似,当调用 useStateuseEffect 时,其内部调用如下:


// In React (simplified a bit)
const React = {
// Real property is hidden a bit deeper, see if you can find it!
__currentDispatcher: null, useState(initialState) {
return React.__currentDispatcher.useState(initialState);
}, useEffect(initialState) {
return React.__currentDispatcher.useEffect(initialState);
}
// ...
};

ReactDOM 提供了 __currentDispatcher(简化的说法):


// In React DOM
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;
let result;
try {
result = YourComponent(props);
} finally {
// Restore it back
React.__currentDispatcher = prevDispatcher;
}

可以看到,Hooks 的原理与 setState 基本一致,但需要注意 reactreact-dom 之间传递了 dispatch,虽然你看不到。但这个 dispatch 必须对应到唯一的 React 实例,这就是为什么 Hooks 不允许同时加载多个 React 实例的原因。

updater 一样,dispatch 也可以被各平台实现重写,比如 react-debug-hooks 就重写了 dispatcher

由于需要同时实现 readContext, useCallback, useContext, useEffect, useImperativeMethods, useLayoutEffect, useMemo, useReducer, useRef, useState,工程量比较浩大,建议了解基本架构就足够了,除非你要深入参与 React 生态建设。

3 精读

与其他 React 分析文章不同,本文并没有过于刨根问题的上来就剖析 reconciler 实现,而是问了一个最基本的疑问:为什么 setState 来自 react 包,但实现却在 react-dom 里?React 是如何实现这个 magic 的?

通过这个疑问,我们了解了 React 更上层的抽象能力,如何用一个包制定规范,用 N 包去实现它。

接口的力量

在日常编程中,接口也拥有的强大力量,下面举几个例子。

UI 组件跨三端的接口

由于 RN、Weex、Flux 的某些不足,越来越多的人选择 “一个思想三端实现” 的方式做跨三端的 UI 组件,这样既兼顾了性能,又可以照顾到平台差异性,对不同平台组件细节做定制优化。

要实施这个方案,最大问题就是接口约定。一定要保证三套实现遵循同一套 API 接口,业务代码才可以实现 “针对任意一个平台编写,自动移植到其他平台”。

比较常用的做法是,通过一套统一的 API 文件约束,固定组件的输入输出,不同平台的组件做平台具体实现。这个思想和 React 如出一辙。

当然 RN 这些框架本身也是同一接口在不同平台实现的典型,只是做的不够彻底,JS 与 Native 的通信导致了性能不如源生。

通用数据查询服务

通用数据查询服务也比较流行,通过磨平各数据库语法,让用户通过一套 SQL 查询各种类型数据库的数据。

这个方案中,一套通用的查询语法就类似 React 定义的 API,执行阶段会转化为各数据库平台的 SQL 方言。

小程序融合方案

现在这种方案很火。通过基于 template 或者 jsx 的语法,一键发布到各平台小程序应用。

这种方案一定会抽象一套通用语法,甚至几乎等价与 reactreact-dom 的关系:所有符合规范的语法,转化为各小程序平台的实现。

4 总结

这种分平台实现方案与跨平台方案还是有很大区别的,像 JAVA 虚拟机本质还是一套实现方案。而分平台的实现可以带来最原生的性能与体验,同样收到的约束也最大,应该其 API 应该是所有平台支持的一个子集。

另外,这种方案不仅可以用于 一套规范,不同平台的实现,甚至可以用在 “同一平台的实现”。

无论是公司还是开源节界,都有许多重复的轮子或者平台,如果通过技术委员会约定一套平台的实现规范,大家都遵循这个规范开发平台,那未来就比较好做收敛,或者说收敛的第一步都是先统一 API 规范。

留下一个思考题:还有没有利用 setState 规范与实现分离的思想案例?欢迎留下你的答案。

讨论地址是:精读《setState 做了什么》 · Issue #122 · dt-fe/weekly

如果你想参与讨论,请点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

来源:https://segmentfault.com/a/1190000017787272

精读《setState 做了什么》的更多相关文章

  1. 精读《V8 引擎 Lazy Parsing》

    1. 引言 本周精读的文章是 V8 引擎 Lazy Parsing,看看 V8 引擎为了优化性能,做了怎样的尝试吧! 这篇文章介绍的优化技术叫 preparser,是通过跳过不必要函数编译的方式优化性 ...

  2. 深入浏览器工作原理和JS引擎(V8引擎为例)

    浏览器工作原理和JS引擎 1.浏览器工作原理 在浏览器中输入查找内容,浏览器是怎样将页面加载出来的?以及JavaScript代码在浏览器中是如何被执行的? 大概流程可观察以下图: 首先,用户在浏览器搜 ...

  3. [翻译] V8引擎的解析

    原文:Parsing in V8 explained 本文档介绍了 V8 引擎是如何解析 JavaScript 源代码的,以及我们将改进它的计划. 动机 我们有个解析器和一个更快的预解析器(~2x), ...

  4. 一文搞懂V8引擎的垃圾回收

    引言 作为目前最流行的JavaScript引擎,V8引擎从出现的那一刻起便广泛受到人们的关注,我们知道,JavaScript可以高效地运行在浏览器和Nodejs这两大宿主环境中,也是因为背后有强大的V ...

  5. Chrome V8引擎系列随笔 (1):Math.Random()函数概览

    先让大家来看一幅图,这幅图是V8引擎4.7版本和4.9版本Math.Random()函数的值的分布图,我可以这么理解 .从下图中,也许你会认为这是个二维码?其实这幅图告诉我们一个道理,第二张图的点的分 ...

  6. (译)V8引擎介绍

    V8是什么? V8是谷歌在德国研发中心开发的一个JavaScript引擎.开源并且用C++实现.可以用于运行于客户端和服务端的Javascript程序. V8设计的初衷是为了提高浏览器上JavaScr ...

  7. 浅谈Chrome V8引擎中的垃圾回收机制

    垃圾回收器 JavaScript的垃圾回收器 JavaScript使用垃圾回收机制来自动管理内存.垃圾回收是一把双刃剑,其好处是可以大幅简化程序的内存管理代码,降低程序员的负担,减少因 长时间运转而带 ...

  8. V8引擎嵌入指南

    如果已读过V8编程入门那你已经熟悉了如句柄(handle).作用域(scope)和上下文(context)之类的关键概念,以及如何将V8引擎作为一个独立的虚拟机来使用.本文将进一步讨论这些概念,并介绍 ...

  9. 浅谈V8引擎中的垃圾回收机制

    最近在看<深入浅出nodejs>关于V8垃圾回收机制的章节,转自:http://blog.segmentfault.com/skyinlayer/1190000000440270 这篇文章 ...

  10. 深入出不来nodejs源码-V8引擎初探

    原本打算是把node源码看得差不多了再去深入V8的,但是这两者基本上没办法分开讲. 与express是基于node的封装不同,node是基于V8的一个应用,源码内容已经渗透到V8层面,因此这章简述一下 ...

随机推荐

  1. [HZOI 2015]复仇的序幕曲

    [题目描述] 你还梦不梦痛不痛,回忆这么重你怎么背得动 ----序言 当年的战火硝烟已经渐渐远去,可仇恨却在阿凯蒂王子的心中越来越深 他的叔父三年前谋权篡位,逼宫杀死了他的父王,用铁血手腕平定了国内所 ...

  2. C++/CLI中的const literal initonly 友元(转)

    C++/CLI中的const literal initonly 友元 C++/CLI中的const Visual C++并不允许在const对象上调用成员函数,除非该成员函数也被声明为const. C ...

  3. C#让窗体在启动时直接隐藏

    最完美的解决办法,不闪烁.思路为:首先将窗体透明度设置为0,这样窗体在启动时就不显示了,然后再调用Hide将窗体隐藏再将其透明度设置为1即可. 其实就设置2个地方就行了. 1:设置窗体透明度为0 2: ...

  4. Java中的阻塞队列-ArrayBlockingQueue(一)

    最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...

  5. Fibonacci(斐波那契数列)的第N位数

    无穷数列1,1,2,3,5,8,13,21,34,55...称为Fibonacci数列,它可以递归地定义为F(n)=1 ...........(n=1或n=2)F(n)=F(n-1)+F(n-2).. ...

  6. (七)JavaScript之[调试]与[前端表单验证]

    12].调试为什么要去调试?1.在编写JavaScript时,如果没有调试工具将是一件很痛苦的事情.2.没有调试工具是很难去编写JavaScript程序的.3.编写的代码可能包含语法错误.逻辑错误,如 ...

  7. spring-cloud构架微服务(2)-全局配置二

    接上篇,实际项目中,可能会遇到有些配置项,例如:邮件地址.手机号等在服务已经上线之后做了改动(就当会出现这种情况好了).然后你修改了配置信息,就得一个一个去重启对应的服务.spring-全局配置提供了 ...

  8. android api 之Scroller

    Scroller是封装了滚动,实现View和ViewGroup的背景画布的滚动. 它有两个构造方法: public Scroller (Context context) 传递一个上下文. public ...

  9. 轻松解决 Eclipse Indigo 3.7 中文字体偏小,完美 Consolas 微软雅黑混合字体!(转)

    在 Windows 7 下初始后化,发现界面变化不大,但中文字体却面目全非,小得根本看不见,而且也看起来很不爽.其实这是 Eclipse 的默认字体换了,以前的一直是 Courier New ,这次e ...

  10. 运行python文件报SyntaxError:Non-ASCII character '\xe7'

    以下是报错内容: 在文件页头加上: #coding=uft-8 ~解决了~ 记录一下(捂脸)