浅谈react

react是什么?其官网给出了明确定义:A JavaScript library for building user interfaces,一个用于构建用户界面的JavaScript库。

1. Thinking in React

通常情况下前端的界面可以用一个简单的公式来抽象:

  view = fn(state)

举个例子,现在有这么一个需求:根据一组信息渲染一个列表

let infos = [
{ name: 'zhangsan' },
{ name: 'lisi' },
{ name: 'wangwu' },
...
] function render() {
// 清空列表
$ul.removeAll();
// 重新渲染列表
infos.forEach(i => $ul.append(generateLi(i)));
}

很完美,只要数据一变执行render方法就好了,坏处是每次都要重新渲染整个页面,而浏览器的渲染和js执行是同一个线程,数据量很大时可能会造成页面的卡顿。现在我们再回过头来看公式,fn是什么?事实上fn是实现数据到视图映射的方法,上面就是一种比较原始的实现。对于react,笔者认为其fn可以表示为:

  function fn() {
virtualDom();
component();
}

1.1 虚拟dom

上文中提到视图是数据的表现,当数据改变时可以清空所有的dom节点然后重新渲染视图,缺点是可能造成了很大的浪费,因为大部分的dom节点可能并没有变,比如渲染一个列表,可能只是插入了一条数据:

那么有没有方法是在渲染之前先进行比较,然后只改变更新的dom节点?虚拟dom正好可以做这件事,虚拟dom是真实dom节点的数据结构映射,只要给出了一定的规范,就可以利用虚拟dom表示真实的dom。举个例子,更新前的列表可以这么表示:

// real dom
<ul>
<li>zhang san</li>
<li>wang wu</li>
<ul>
// virtual dom
{
tag: 'ul',
children: [
{
tag: 'li',
text: 'zhang san'
},
{
tag: 'li',
text: 'wang wu'
}
]
}

同样的更新后的列表依然可以使用虚拟dom表示,虚拟dom之间的diff是十分快的,只要把虚拟dom的差异映射到真实dom节点上即可完成视图的更新。

1.2 组件化

谈到组件化首先想到的是代码复用,但组件化不止如此。虚拟dom只是优化了数据到视图的映射方式,但是当数据改变时,应该选择什么样的范围进行diff其并没有给出。假设我们的页面结构如下:

想象一下,假如仅仅因为list插入了一条数据就对整个视图diff,即使js引擎很快但这样的效率无疑也是很低的,我们更希望的是将数据拆分,每次数据改变只对一个可控的范围进行diff,而其余的部分不受影响。组件化刚好解决了这样的问题,我们可以将页面拆分为如下:

<div>
<Sider />
<div>
<Header />
<List />
<Footer />
</div>
</div>

通过划分组件,可以将视图隔离为相互独立的部分,List组件的状态改变时只要对List进行diff即可,而其余的组件不受影响。所以组件化让我们有了定义diff粒度的能力,提高了数据变化时视图的更新效率。

2. React渲染流程

注:本文没有考虑fiber架构

2.1 组件渲染流程

一个React应用可以看做是一个相对较大的组件,所以标题React渲染流程更多是指一个组件的渲染流程。组件的渲染流程可简单的分为两步:

  • 初始渲染
  • 更新渲染

2.1.1 初始渲染

初始渲染的流程相对简单,根据状态构建虚拟dom,根据虚拟dom渲染真实dom:

state => virtual dom => view

虚拟dom的构建是在render方法中进行的,首先需要明确jsx语法只是React.createElement的语法糖

// JSX
<div>
<p>this is parent</p>
<ChildComponent msg={ msg } />
</div>
// Babel转译之后
React.createElement(
"div",
null,
React.createElement(
"p",
null,
"this is parent"
),
React.createElement(ChildComponent, { msg: msg })
);

可以猜测,React.createElement就是构造虚拟dom的方法,事实上上例会返回一个类似如下的虚拟dom,然后根据虚拟dom深度优先构建真实dom树

2.1.2 更新渲染

更新渲染时首先会对更新前后的虚拟dom进行diff,然后将差异patch到真实dom即可完成组件的更新。虚拟dom进行diff时会根据类型的不同采取不同的策略,笔者根据虚拟domnodeName类型的不同将其划分为两种:

  • html vnode

    原生dom标签对应的虚拟dom
  • component vnode

    组件标签对应的虚拟dom

参考上面例子,html vnode对应:

component vnode对应:

html vnode的更新相对简单,可以简单的理解为对比虚拟dom的nodeName,相同表明真实dom可以复用,只更新属性和子节点即可,不同则创建新的dom并删除掉旧的dom节点。

component vnode的diff相对复杂,也可以近似理解为先对比虚拟dom的nodeName是否相同,如果相同则走组件更新对应的生命周期,反之旧虚拟dom对应的组件会被unmount,同时实例化新虚拟dom对应的组件。这里需要注意component vnode的nodeName指向的是组件的构造函数,我们在写jsx时要确保组件的标签名指向的确实是同一个组件,否则可能会出现组件的状态丢失,除非你有意为之。

2.2 组件更新时机

什么行为会导致触发组件更新?

  • 组件执行了setState
  • 组件的父组件执行了rerender

我们知道,只要执行了setState组件就会执行更新过程,但是父组件rerender一定会引起子组件rerender吗?结论是肯定的,react中只要某一组件的状态发生改变,就会以该组件为根重新渲染整个组件树,即使子组件的props没有发生改变。这似乎很傻,因此react暴露了shouldComponentUpdate方法给我们手动控制组件是否渲染:

shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}

shouldComponentUpdate返回false时就会跳过组件更新流程。

3. PureComponent和Immutable

在使用redux时我们知道reducer必须是pure function,纯函数有两个显著的特点:

  • 相同的输入必然会得到相同的输出
  • 函数是无副作用的,不依赖于外部状态也不会改变外部状态

抽象点来看组件其实也是一个函数,它接收两个参数propsstate,返回一个view。借鉴纯函数的概念,PureComponent显然是指当propsstate不变时渲染相同视图的组件,其原理类似是一个高阶组件,内部实现了shouldComponentUpdate方法,当propsstate不变时会跳过组件更新过程,省去了虚拟dom生成和diff的过程。这样看来似乎使用PureComponent可以极大的提供react的性能,但是这里还有一个重要的前提:PureComponent执行的是浅比较,稍不注意可能会出现问题,看下面的代码:

class List extends PureComponent {
render() {
const list = this.props.list;
return (
<ul>
{
list.map(i => {
return <li>{ i.now }</li>
})
}
</ul>
)
}
} class App extends Component {
state = {
list: [{ now: new Date().getTime() }]
} add = () => {
const list = this.state.list;
list.push({ now: new Date().getTime() });
this.setState({ list })
} render() {
const list = this.state.list;
return (
<div>
<List list={list} />
<button onClick={this.add}>time log</button>
</div>
)
}
}

上例中List是一个PureComponent,其props中list是一个复杂数据结构(数组),而PureComponent只会对props进行浅比较,本例中list的指向不会发生改变,因此无论怎么点击button组件都不会更新。

那么应该什么时候使用PureComponent呢?

  • 显然当组件的propsstate为简单类型时必然可以使用
  • 当组件的propsstate为复杂类型时保证数据的Immutable

理论上我们可以改变state的唯一途径就是执行setState方法,只要保证setState是immutble的即可,结合上例:

class App extends Component {
... add = () => {
const list = this.state.list;
const newList = [...list, { now: new Date().getTime() }];
this.setState({ list: newList });
} ...
}

事实上setState还可以传入一个函数,参数是当前的state,返回值为新的state:

setState(updater[, callback])

类似于redux中reducer,上例可改写为:

const genListUpdater = payload => state => {
const list = state.list;
return {
list: [...list, payload]
};
} class App extends Component {
... add = () => {
this.setState(genListUpdater({ now: new Date().getTime() }));
} ...
}

但是还有一个问题,当数据的嵌套层级较深时保持数据的immutable很费劲,根据具体情况可以考虑使用ImmutableJs(https://facebook.github.io/immutable-js/)处理。

浅谈React的更多相关文章

  1. 【转】浅谈React、Flux 与 Redux

    本文转自<浅谈React.Flux 与 Redux>,转载请注明出处. React React 是一个 View 层的框架,用来渲染视图,它主要做几件事情: 组件化 利用 props 形成 ...

  2. 浅谈React工作原理

    浅谈React工作原理:https://www.cnblogs.com/yikuu/p/9660932.html 转自:https://cloud.tencent.com/info/63f656e0b ...

  3. 浅谈React数据流管理

    引言:为什么数据流管理如此重要?react的核心思想就是:UI=render(data),data就是我们说的数据流,render是react提供的纯函数,所以用户界面的展示完全取决于数据层.这篇文章 ...

  4. 浅谈React受控与非受控组件

    背景 React内部分别使用了props, state来区分组件的属性和状态.props用来定义组件外部传进来的属性, 属于那种经过外部定义之后, 组件内部就无法改变.而state维持组件内部的状态更 ...

  5. 浅谈react的初步试用

    现在最热门的前端框架,毫无疑问是 React . 上周,基于 React 的 React Native 发布,结果一天之内,就获得了 5000 颗星,受瞩目程度可见一斑. React 起源于 Face ...

  6. 浅谈react受控组件与非受控组件

    引言 最近在使用蚂蚁金服出品的一条基于react的ant-design UI组件时遇到一个问题,编辑页面时input输入框会展示保存前的数据,但是是用defaultValue就是不起作用,输入框始终为 ...

  7. 浅谈 React

    机缘巧合认识React,翻了2天的资料,又整理了1天,也算是简单入门了;之前也学过angular,相比来说,的确React代码逻辑更加简单明了,理解起来也相对容易. React 具备以下特性:1.声明 ...

  8. 浅谈React、Vue 部分异步

    React中的setState setState为什么需要异步? 无法限制何时使用异步,多次连续使用setState 防止多次渲染,异步rendering不仅仅是性能上的优化,而且这可能是react组 ...

  9. 浅谈React和VDom关系

    组件化 组件的封装 组件的复用 组件的封装 视图 数据 视图和数据之间的变化逻辑 import React, {Component} from 'react'; export default clas ...

随机推荐

  1. Flink写入kafka时,只写入kafka的部分Partitioner,无法写所有的Partitioner问题

    1. 写在前面 在利用flink实时计算的时候,往往会从kafka读取数据写入数据到kafka,但会发现当kafka多个Partitioner时,特别在P量级数据为了kafka的性能kafka的节点有 ...

  2. 《连载 | 物联网框架ServerSuperIO教程》- 16.集成OPC Server,及使用步骤。附:3.3 发布与版本更新说明。

    1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架Serve ...

  3. yum程序下载被占用

    Loaded plugins: fastestmirror, refresh-packagekit, security Existing lock /var/run/yum.pid: another ...

  4. Ubuntu 服务器设置软件多用户访问

    假设在用户A下安装了软件xx 路径写入$home/.bashrc 这时该软件只有该用户可以使用 若要其他用户也能使用,只需要将该.bashrc拷贝到其他user的$home目录就行了

  5. 1. Linux系统介绍

    1. 什么是操作系统? 定义:操作系统是计算机系统中必不可少的基础系统软件,它的作用是负责管理和控制计算机系统中的硬件和软件资源,合理地组织计算机系统的工作流程,以便有效地利用资源为使用者提供一个功能 ...

  6. BZOJ 3622

    直接算 $a_i>b_i$ 对数恰为 $k$ 的不好算 那么可以先算 $a_i>b_i$ 对数至少 $k$ 的 这个排序后随便dp一下就好 那么再除了一下 用 $f_i$ 表示 $a_i& ...

  7. Html开发中document.getElementByTagName无法找到所有DOM元素的问题解决方法

    let eleList = document.querySelectorAll('li') for (let i = 0; i < eleList.length; i++) { // 遍历操作 ...

  8. springboot排除exclude

    @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})

  9. SparkStreaming+Kafka整合

    SparkStreaming+Kafka整合 1.需求 使用SparkStreaming,并且结合Kafka,获取实时道路交通拥堵情况信息. 2.目的 对监控点平均车速进行监控,可以实时获取交通拥堵情 ...

  10. mybatis 之数据库 include refid ="base_column_list"

    mybatis 之数据库 include refid ="base_column_list" 对于刚学习使用SSM框架的新手来说,mybatis中的数据库语句有点不一样,下面便是对 ...