本文翻译自:How Does setState Know What to Do?

原作者:Dan Abramov

如果有任何版权问题,请联系shuirong1997@icloud.com

当你在组件中调用setState时,你觉得会发生什么?

js导出excel

import React from 'react';
import ReactDOM from 'react-dom'; class Button extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ clicked: true });
}
render() {
if (this.state.clicked) {
return <h1>Thanks</h1>;
}
return (
<button onClick={this.handleClick}>
Click me!
</button>
);
}
} ReactDOM.render(<Button />, document.getElementById('container'));

当然,React会用{ clicked: true} 这条状态重新渲染组件并且更新匹配到的DOM,然后返回<h1>Thanks</h1>元素。

听起来似乎简洁明了。但别急,React(或者说React DOM)是怎么做的?

更新DOM听起来像是React DOM的事儿,但别忘了我们调用的可是this.setState(),它是React的东西,可不是React DOM的。另外,我们的基类React.Component是被定义在React内部。

所以问题来了:React.Component内部的setState怎么能去更新DOM呢?

事先声明:就像我的其他博客,你不需要熟练掌握React。这篇博客是为那些想要看看面纱之后是什么东西的人准备的。完全可选!


我们或许会认为React.Component类已经包含了DOM更新逻辑。

但如果这是事实,那this.setState是如何工作在其他环境中呢?比如:在React Native App中的组件也能继承React.Component,他们也能像上面一样调用this.setState(),并且React Native工作在Android和iOS的原生视图而不是DOM中。

你可能也对React Test Renderer 或 Shallow Renderer比较熟悉。这两个测试渲染器让你可以渲染一般的组件并且也能在他们中调用this.setState,但他们可都不使用DOM。

如果你之前使用过一些渲染器比如说React ART,你可能知道在页面中使用超过一个渲染器是没什么问题的。(比如:ART组件工作在React DOM 树的内部。)这会产生一个不可维持的全局标志或变量。

所以React.Component以某种方式将state的更新委托为具体的平台(译者注:比如Android, iOS),在我们理解这是如何发生之前,让我们对包是如何被分离和其原因挖得更深一点吧!


这有一个常见的错误理解:React "引擎"在react包的内部。这不是事实。

事实上,从 React 0.14开始对包进行分割时,React包就有意地仅导出关于如何定义组件的API了。React的大部分实现其实在“渲染器”中。

渲染器的其中一些例子包括:react-dom,react-dom/server,react-native,react-test-renderer,react-art(另外,你也可以构建自己的)。

这就是为什么react包帮助很大而不管作用在什么平台上。所有它导出的模块,比如React.ComponentReact.createElementReact.Children[Hooks](https://reactjs.org/docs/hooks-intro.html),都是平台无关的。无论你的代码运行在React DOM、React DOM Server、还是React Native,你的组件都可以以一种相同的方式导入并且使用它们。

与之相对的是,渲染器会暴露出平台相关的接口,比如ReactDOM.render(),它会让你可以把React挂载在DOM节点中。每个渲染器都提供像这样的接口,但理想情况是:大多数组件都不需要从渲染器中导入任何东西。这能使它们更精简。

大多数人都认为React“引擎”是位于每个独立的渲染器中的。许多渲染器都包含一份相同的代码—我们叫它“调节器”,为了表现的更好,遵循这个步骤 可以让调节器的代码和渲染器的代码在打包时归到一处。(拷贝代码通常不是优化“打包后文件”(bundle)体积的好办法,但大多数React的使用者一次只需要一个渲染器,比如:react-dom(译者注:因此可以忽略调节器的存在))

The takeaway here 是react包仅仅让你知道如何使用React的特性而无需了解他们是如何被实现的。渲染器(react-dom,react-native等等)会提供React特性的实现和平台相关的逻辑;一些关于调节器的代码被分享出来了,但那只是单独渲染器的实现细节而已。


现在我们知道了为什么reactreact-dom包需要为新特定更新代码了。比如:当React16.3新增了Context接口时,React.createContext()方法会在React包中被暴露出来。

但是React.createContext()实际上不会实现具体的逻辑(译者注:只定义接口,由其他渲染器来实现逻辑)。并且,在React DOM和React DOM Server上实现的逻辑也会有区别。所以createContext()会返回一些纯粹的对象(定义如何实现):

// 一个简单例子
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;
}
``` 你会在某处代码中使用<MyContext.Provider><MyContext.Consumer>,那里就是决定着如何处理他们的渲染器。React DOM会用A方法追踪context值,但React DOM Server或许会用另一个不同的方法实现。 所以如果你将react升级到16.3+,但没有升级react-dom,你将使用一个还不知道ProviderConsumer类型的渲染器,这也就旧版的react-dom可能会报错:fail saying these types are invalid的原因。 同样的警告也会出现在React Native中,但是不同于React DOM,一个新的React版本不会立即产生一个对应的React Native版本。他们(React Native)有自己的发布时间表。大概几周后,渲染器代码才会单独更新到React Native库中。这就是为什么新特性在React Native生效的时间会和React DOM不同。
Okay,那么现在我们知道了react包不包含任何好玩的东西,并且具体的实现都在像react-domreact-native这样的渲染器中。但这并不能回答我们开头提出的问题。React.Component里的setState()是如何和对应的渲染器通信的呢? 答案是每个渲染器都会在创建的类中添加一个特殊的东西,这个东西叫updater。它不是你添加的东西—恰恰相反,它是React DOM,React DOM Server 或者React Native在创建了一个类的实例后添加的:
// React DOM 中是这样
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater;
// React DOM Server 中是这样
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater;
// React Native 中是这样
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;

setState的实现就可以看出,它做的所有的工作就是把任务委托给在这个组件实例中创建的渲染器:

// 简单例子
setState(partialState, callback) {
// 使用`updater`去和渲染器通信
this.updater.enqueueSetState(this, partialState, callback);
}
``` React DOM Server 可能想忽略状态更新并且警告你,然而React DOM和React Native将会让调节器的拷贝部分去 处理它。 这就是尽管this.setState()被定义在React包中也可以更新DOM的原因。它调用被React DOM添加的this.updater并且让React DOM来处理更新。
现在我们都比较了解“类”了,但“钩子”(Hooks)呢? 当人们第一次看到 钩子接口的提案时,他们常回想:useState是怎么知道该做什么呢?这一假设简直比对this.setState()的疑问还要迷人。 但就像我们如今看到的那样,setState()的实现一直以来都是模糊不清的。它除了传递调用给当前的渲染器外什么都不做。所以,useState钩子做的事也是如此。 这次不是updater,钩子(Hooks)使用一个叫做“分配器”(dispatcher)的对象,当你调用React.useState()React.useEffect()或者其他自带的钩子时,这些调用会被推送给当前的分配器。
// 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);
},
// ...
};

单独的渲染器会在渲染你的组件之前设置分配器(dispatcher)。

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

React DOM Server的实现在这里。由React DOM和React Native共享的调节器实现在这里

这就是为什么像react-dom这样的渲染器需要访问和你调用的钩子所使用的react一样的包。否则你的组件将找不到分配器!如果你有多个React的拷贝在相同的组件树中,代码可能不会正常工作。然而,这总是造成复杂的Bug,因此钩子会在它耗光你的精力前强制你去解决包的副本问题。

如果你不觉得这有什么,你可以在工具使用它们前精巧地覆盖掉原先的分配器(__currentDispatcher的名字其实我自己编的但你可以在React仓库中找到它真正的名字)。比如:React DevTools会使用一个特殊的内建分配器来通过捕获JavaScript调用栈来反映(introspect)钩子。不要在家里重复这个(Don’t repeat this at home.)(译者注:可能是“不要在家里模仿某项实验”的衍生体。可能是个笑话,但我get到)

这也意味着钩子不是React固有的东西。如果在将来有很多类库想要重用相同的基础钩子,理论上来说分配器可能会被移到分离的包中并且被塑造成优秀的接口—会有更少让人望而生畏的名称—暴露出来。在实际中,我们更偏向去避免过于仓促地将某物抽象,直到我们的确需要这么做。

updater__currentDispatcher都是泛型程序设计(依赖注入/dependency injection)的绝佳实例。渲染器“注入”特性的实现。就像setState可以让你的组件看起来简单明了。

当你使用React时,你不需要考虑它是如何工作的。我们期望React用户去花费更多的时间去考虑它们的应用代码而不是一些抽象的概念比如:依赖注入。但如果你曾好奇this.setState()useState()是怎么知道它们该做什么的,那我希望这篇文章将帮助到你。

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

js表格打印自动分页demo的更多相关文章

  1. 前端js实现打印excel表格

    产品原型: 图片.png 功能需求:点击导出考勤表格按钮,会自动下载成Excel格式 图片.png 图片.png jsp页面代码: <div class="tools"> ...

  2. 前端js实现打印(导出)excel表格

    产品原型: 图片.png 功能需求:点击导出考勤表格按钮,会自动下载成Excel格式 图片.png 图片.png jsp页面代码: <div class="tools"> ...

  3. JS 网页打印解决方案

    这些日子真是太忙了,项目太多了公司总是加班,而且这些项目中好多都用到的打印,所以学习了一段时间js的打印. 其实原来只是用到了简单的功能,现在要深入的了解才发现原来ie的网页打印也是如此的强大. 以下 ...

  4. js插件---JS表格组件BootstrapTable行内编辑解决方案x-editable

    js插件---JS表格组件BootstrapTable行内编辑解决方案x-editable 一.总结 一句话总结:bootstrap能够做为最火的框架,绝对不仅仅只有我看到的位置,它应该还有很多位置可 ...

  5. 网站开发进阶(十二)JS实现打印功能(包括打印预览、打印设置等)

    JS实现打印功能(包括打印预览.打印设置等) 绪 最近在进行项目开发时,需要实现后台管理端打印功能,遂在网上一阵搜索,搜到了很多相关的文章.其中绝大部分文章都是使用的Lodop5.0(Web打印和套打 ...

  6. 菜鸟云打印接入Demo

    菜鸟云打印接入Demo 0. 接入流程图 1. 连接打印客户端 首先要打开打印客户端,然后使用下面的方法,连接客户端(WebSocket协议): 地址 :  连接打印客户端 function doCo ...

  7. js 表格操作----添加删除

    js 表格操作----添加删除 书名:<input type="text" id="name"> 价格:<input type="t ...

  8. 用js实现打印九九乘法表

    用js在打印九九乘法表 思考 在学习了流程控制和条件判断后,我们可以利用js打印各式各样的九九乘法表 不管是打印什么样三角形九九乘法表,我们都应该找到有规律的地方,比如第一列的数字是什么规律,第一行的 ...

  9. firefox和chrome实现页面打印自动分页

    在Firefox和chrome中直接调用打印功能的js方法是 window.print(); 但是如果页面很长,那么就需要分页,这时只需要在页面中添加css属性即可,如果想自动分页,则如下所示 < ...

随机推荐

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

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

  2. C# 和 Linux 时间戳转换

            /// <summary>         /// 时间戳转为C#格式时间         /// </summary>         /// <par ...

  3. 前端性能优化-keep-alive

    什么是Keep-Alive Keep-Alive是浏览器端和服务器端约定的一种提高传输效率的协议.我先举个例子吧,我现在搬家,有10个箱子,如果我自己来搬的话,每次只能带一个箱子,那么搬到目的地,需要 ...

  4. Python并发编程之进程池与线程池

    一.进程池与线程池 python标准模块concurrent.futures(并发未来) 1.concurrent.futures模块是用来创建并行的任务,提供了更高级别的接口,为了异步执行调用 2. ...

  5. html5 chrome 摄像头 &&bootstrap

    <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...

  6. Stuts2的 "struts.devMode" 设置成true后,不起作用,仍需要重启tomcat

    不要用 <constant name="struts.devMode" value="true" />改成: <constant name=& ...

  7. java之Socket传递图片

    客户端: package client; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import ...

  8. SQL varchar转float实现数字比较

    select * from table where cast('经纬度' as float ) < 90

  9. hive自定义UDTF函数叉分函数

    hive自定义UDTF函数叉分函数 1.介绍 从聚合体日志中需要拆解出来各子日志数据,然后单独插入到各日志子表中.通过表生成函数完成这一过程. 2.定义ForkLogUDTF 2.1 HiveUtil ...

  10. HTML5开发,背后的事情你知道吗?

    现在的H5越来越受到企业或者是开发者的一个大力的追捧,已经成为网络推广必不可少的一个使用的工具,相信还有很多朋友现在都不知道H5是个什么东西,本文将为大家讲的是关于H5一些分类的问题,让你进一步的去学 ...