1. React的优缺点
优点
- 虚拟 DOM 加 diff 算法减少 DOM 操作提升渲染性能
- 一切皆组件,代码更加模块化,复用代码更加容易
- 单向数据流,灵活,可预计,可控制
- 能够很好的和现有的代码结合,React 只是 MVC 中的 View 层,对他部分没有硬性要求。对项目重构的时候可以选择部分重构
- 可以实现服务器渲染,便于搜索引擎优化
- 强调只从 this.props 和 this.state 生成 HTML,非常的函数式编程
缺点
- 并非一个完整的框架,基本需要加上 React-router ,redux 才能写出大型应用
2. 虚拟 DOM
2.1 什么是虚拟 DOM
虚拟 DOM 本质就是用一个原生的 Javascript
对象去描述一个 DOM节点,是对真实 DOM 的一层抽象。简单说就是一个普通的 JavaScript
对象,包含了 tag
,props
,children
三个属性
例如:
<div id="app">
<p class="text">hello world!</p>
</div>
转换为虚拟 DOM
{
tag: 'div',
props: {
id: 'app'
},
chidren: [
{
tag: 'p',
props: {
className: 'text'
},
chidren: [
'hello world!!!'
]
}
]
}
该对象就是我们常说的虚拟 DOM 了,因为 DOM 是树形结构,所以使用 JavaScript
对象就能很简单的表示。
2.2 为什么要有虚拟 DOM
原生 DOM 因为浏览器厂商需要实现众多的规范(各种 HTML5 属性、DOM事件),即使创建一个空的 div 也要付出昂贵的代价。虚拟 DOM 提升性能的点在于 DOM 发生变化的时候,通过 diff 算法比对 JavaScript 原生对象,计算出需要变更的 DOM,然后只对变化的 DOM 进行操作,而不是更新整个视图。在浏览器中频繁地操作 DOM 是很消耗性能的。因此,我们需要一层抽象。尽可能地一次性将差异更新到 DOM 中。同时也是为了更好的跨平台,比如 Node 中就不存在 DOM,如果想要实现 SSR,一个方式就是借助虚拟 DOM,因为虚拟 DOM 本身就是 Javascript 对象。
3. DOM Diff 算法解析
3.1 什么是 DOM Diff 算法
Web界面由DOM树来构成,当其中某一部分发生变化时,其实就是对应的某个DOM节点发生了变化。在React中,构建UI界面的思路是由当前状态决定界面。前后两个状态就对应两套界面,然后由React来比较两个界面的区别,这就需要对DOM树进行Diff算法分析。
即给定任意两棵树,找到最少的转换步骤。但是标准的的Diff算法复杂度需要O(n^3),这显然无法满足性能要求。Facebook工程师结合Web界面的特点做出了两个简单的假设,使得Diff算法复杂度直接降低到O(n)。
-
两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构
-
对于同一层次的一组子节点,它们可以通过唯一的id进行区分
3.2 不同节点类型的比较
3.2.1 节点类型不同
当在树中的同一位置前后输出了不同类型的节点,React直接删除前面的节点,然后创建并插入新的节点。
例如当一个节点从div变成span时,简单的直接删除div节点,并插入一个新的span节点。当然,删除节点意味着彻底销毁该节点,而不是再后续的比较中再去看是否有另外一个节点等同于该删除的节点。如果该删除的节点之下有子节点,那么这些子节点也会被完全删除,它们也不会用于后面的比较。这也是算法复杂能够降低到O(n)的原因。与其浪费时间去比较它们基本上不会等价的DOM结构,还不如完全创建一个新的组件加上去。
React的DOM Diff算法实际上只会对树进行逐层比较
3.2.2 节点类型相同,但是属性不同
React会对属性进行重设从而实现节点的转换
列表节点的比较
列表节点的操作通常包括添加、删除和排序。如果每个节点都没有唯一的标识,React 无法识别每一个节点,那么更新过程会很低效。React 会逐个对节点进行更新,转换到目标节点。涉及到的 DOM 操作非常多。而如果给每个节点唯一的标识(key),那么React能够找到正确的位置去插入新的节点。对于列表节点提供唯一的key属性可以帮助React定位到正确的节点进行比较,从而大幅减少 DOM 操作次数,提高了性能。
3. React 理念
- 函数式编程
- 声明式开发
- 可以与其他框架并存
- 组件化
- 单向数据流
- 视图层框架
4. React 重新 render 之后
5. 哪些方法会触发 React 重新渲染
state 或者 props 发生改变的时候,React 就会重新渲染
使用 shouldComponentUpdate
方法,可以让 React 判断是否应该渲染。shouldComponentUpdate
方法默认返回true
,这就是导致每次更新都重新渲染的原因。但是你可以在需要优化性能时重写这个方法来让React更智能。当React将要渲染组件时它会执行shouldComponentUpdate
方法来看它是否返回true
(组件应该更新,也就是重新渲染)。所以你需要重写shouldComponentUpdate
方法让它根据情况返回true
或者false
来告诉React什么时候重新渲染什么时候跳过重新渲染。
6. state 和 props 触发更新的生命周期有什么区别
16.3 之前
props 发生改变会触发 componentWillReceiveProps(),而 state 不会
16.3 之后
props 发生改变会触发 getDerivedStateFromProps(),而 state 不会
7. setState 是同步还是异步
setState 并没有异步的说法,之所以给人感觉是一种异步的表现,是因为 React 框架本身的性能机制导致的。 因为每一次调用 setState 都会触发更新,异步操作是为了提高性能,将多个状态合并一起更新,减少 render 调用。React会将多个setState的调用合并为一个来执行,也就是说,当执行setState的时候,state中的数据并不会马上更新。
setState 在 React 的生命周期函数或者作用域下表现为异步,在原生的环境下表现为同步。
8. React无状态组件和有状态组件
8.1 无状态组件(展示组件)
主要用来定义模板,接收来自父组件 props 传递过来的数据,使用 {props.xxx}
的表达式把 props 塞到模板里面。无状态组件应该保持模板的纯粹性,以便于组件复用。
var Header = (props) = (
<div>{props.xxx}</div>
);
8.2 有状态组件(容器组件)
主要用来定义交互逻辑和业务数据,使用 {this.state.xxx}
的表达式把业务数据挂载到容器组件的实例上,然后传递 props
到展示组件,展示组件接收到 props
,把 props
塞到模板里面。
class Home extends React.Component {
constructor(props) {
super(props);
};
render() {
return (
<Header />
)
}
}
9. React16.3.0之前的生命周期
9.1 挂载
-
constructor() (设置组件的初始化属性和状态)
-
componentWillMount()
-
render
-
componentDidMount()(组件挂载后(插入 DOM 树中)立即调用,可以进行网络请求)
9.2 更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
9.2.1 props 发生改变时
-
componentWillReceiveProps()
-
shouldComponentUpdate()
-
componentWillUpdate()
-
render
-
componentDidUpdate()(更新后会被立即调用,首次渲染不会执行此方法)
9.2.2 state 发生改变时
-
shouldComponentUpdate() (根据返回值判断组件是否应该重新渲染)
-
componentWillUpdate()
-
render
-
componentDidUpdate()
9.3 卸载
- componentWillUnmount()(组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在
componentDidMount()
中创建的订阅)
10. React16.3.0之后的生命周期
10.1 挂载
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
- constructor()
- static getDerivedStateFromProps()(返回一个对象更新 state)
- render()
- componentDidMount()(组件挂载后(插入 DOM 树中)立即调用)
10.2 更新
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
10.2.1 props 发生变化时
- static getDerivedStateFromProps()(在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。)
- shouldComponentUpdate()(根据返回值判断组件是否应该重新渲染)
- render
- getSnapshotBeforeUpdate()(最近一次渲染输出(提交到 DOM 节点)之前调用。使得组件能在发生更改之前从 DOM 中捕获一些信息)
- componentDidUpdate()
10.2.2 state 发生变化时
-
shouldComponentUpdate()(根据返回值判断组件是否应该重新渲染)
-
render
-
getSnapshotBeforeUpdate()(最近一次渲染输出(提交到 DOM 节点)之前调用。使得组件能在发生更改之前从 DOM 中捕获一些信息)
-
componentDidUpdate()
9.3 卸载
- componentWillUnmount()(组件卸载及销毁之前直接调用)
9.4 错误处理
- static getDerivedStateFromError()(后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state)
- componentDidCatch()(后代组件抛出错误后被调用)
10. React 性能优化
- 要尽可能少地在 render() 函数中做一些复杂操作或计算。
- 避免在组件的 state 上存储一些容易计算的值,如果一个值可以通过简单的字符串拼接或基本的算数运算从 props 中派生出来,那没有什么必要将值包含在组件的 state 上。
- 函数的绑定
/*
给 prop 传入一个行内绑定的函数(包括 ES6 箭头函数)实质上是在每次父组件 render 时传入一个新的函数。
*/
render() {
return (
<div>
<a onClick={ () => this.doSomething() }>Bad</a>
<a onClick={ this.doSomething.bind( this ) }>Bad</a>
</div>
);
}
/*
应该在构造函数中处理函数绑定并且将已经绑定好的函数作为 prop 的值
*/
constructor( props ) {
this.doSomething = this.doSomething.bind( this );
//or
this.doSomething = (...args) => this.doSomething(...args);
}
render() {
return (
<div>
<a onClick={ this.doSomething }>Good</a>
</div>
);
}
- 对象或数组字面量
/*
对象或者数组字面量在功能上来看是调用了 Object.create() 和 new Array()。这意味如果给 prop 传递了对象字面量或者数组字面量。每次render 时 React 会将他们作为一个新的值。这在处理 Radium 或者行内样式时通常是有问题的。
*/
/* Bad */
// 每次渲染时都会为 style 新建一个对象字面量
render() {
return <div style={ { backgroundColor: 'red' } }/>
}
/* Good */
// 在组件外声明
const style = { backgroundColor: 'red' };
render() {
return <div style={ style }/>
}
- 兜底值字面量
/*
有时我们会在 render 函数中创建一个兜底的值来避免 undefined 报错。在这些情况下,最好在组件外创建一个兜底的常量而不是创建一个新的字面量。
/*
/* Bad */
render() {
let thingys = [];
// 如果 this.props.thingys 没有被定义,一个新的数组字面量会被创建
if( this.props.thingys ) {
thingys = this.props.thingys;
}
return <ThingyHandler thingys={ thingys }/>
}
/* Bad */
render() {
// 这在功能上和前一个例子一样
return <ThingyHandler thingys={ this.props.thingys || [] }/>
}
/* Good */
// 在组件外部声明
const NO_THINGYS = [];
render() {
return <ThingyHandler thingys={ this.props.thingys || NO_THINGYS }/>
}
- 尽可能的保持 props 和 state 的简单,避免传入复杂的对象
- 使用 React.PureComponent 替换 React.Component,React.PureComponent声明了它自己的 shouldComponentUpdate() 方法,它自动对当前的和下一次的 props 和 state 做一次浅对比。
- 使用组件性能分析
- 使用 setTimeout() 和 setInterval(),不要设置过短的时间间隔,并且在组件卸载的时候取消或者销毁、
11. 虚拟 DOM 比真实 DOM 性能好
-
虚拟DOM不会进行排版与重绘操作
-
虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
-
真实DOM频繁排版与重绘的效率是相当低的
-
虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部
12. React 常见的通信方式
12.1 父子组件之间通信 props
12.2 EventEmitter 事件总线机制(兄弟组件通信)
需要安装 events
eventBus.js
import { EventEmitter } from 'events'
export default new EventEmitter();
component1.js
import React, { Component } from 'react';
import Bus from '../eventBus'
class Home extends Component {
state = {
bus: ''
}
componentDidMount() {
Bus.addListener('changeData', (msg) => {
this.setState({
bus: msg
})
})
}
render() {
return (
<div>Bus : {this.state.bus}</div>
)
}
}
export default Home
component2.js
import React, { Component } from 'react';
import Bus from '../eventBus'
class Home extends Component {
state = {
msg: 'newData'
}
handleClick = () => {
Bus.emit('changeData', this.state.msg)
}
render() {
return (
<div onClick={this.handleClick}>newBus</div>
)
}
}
export default Home
12.3 状态共享 redux
13. React Fiber 原理
以前 React 15 的时候,当页面元素很多,且需要频繁刷新的场景下,会出现掉帧现象。其原因就是大量的同步计算任务阻塞了浏览器的 UI渲染。默认情况下,JS 运算,页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥关系。如果 JS 运算占用主线程,页面就没法得到及时的更新。调用 setState 更新页面的时候,React会遍历应用的所有节点,计算出差异,然后再更新 UI。整个阶段是不能被打断的。如果页面元素很多,那就很容易出现掉帧的现象。
解决主线程长时间被 JS 运算占用这一问题的基本思路,是将运算切割为多个步骤,分批完成。意思是在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间进行页面渲染。等浏览器忙完之后,再继续之前未完成的任务。
Fiber 其实是一种数据结构,可以用纯 JS 对象来表示:
const fiber = {
stateNode, // 节点实例
child, // 子节点
sibling, // 兄弟节点
return, // 父节点
}
Fiber 实现了自己的组件调用栈,以链表的形式遍历组件树,可以灵活的暂停,继续和丢弃执行的任务。使用的是浏览器的 requestIdleCallback
这一 API。
14. Filber渲染树
15. React 高阶组件
高阶组件就是接收一个组件作为参数,然后返回一个新的组件。高阶组件其实是一个函数,并不只是一个组件。
16. React 事件
16.1 事件响应
- React 在构建虚拟 DOM 的同时,还构建了自己的事件系统;所有事件对象和
W3C
规范保持一致 - React的事件系统和浏览器事件系统相比,主要增加了两个特性:事件代理,事件自动解绑
16.2 事件代理
- 区别于浏览器事件处理方式,React并未将事件处理函数与对应的 DOM 节点直接关联。而是在顶层使用一个全局事件监听器监听所有事件
- React 会在内部维护一个映射表记录事件与组件处理函数的对应关系
- 当某个事件触发时,React 根据这个内部映射表将事件分派给指定的事件处理函数
- 若映射表中没有事件处理函数时,React 不会做任何操作
- 当组件安装或卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除
16.3 事件自动绑定
- React 的每个事件处理回调函数都会自动绑定到组件实例(使用 ES6 语法创建的例外)。事件回调函数中的 this 指向的是组件实例而不是 DOM 元素
16.4 合成事件
- 是对浏览器原生事件的封装,与浏览器原生事件有同样的接口,如
stopPropagation(),preventDefault()
等,并且这些接口是跨浏览器兼容的。
React
并不是将事件绑在元素的真实 DOM
上,而是在 document
处监听所有支持的事件,当事件发生并冒泡至 document
处时,React将事件内容封装并交由真正的处理函数运行
17. React 异步渲染,Time Slicing 和 Suspense
提出主要是用来解决:CPU速度和网络 IO 问题
17.1 Time Slicing(时间切片)
- React 在渲染(render)的时候,不会阻塞现在的线程
- 如果你的设备足够快,你会感觉渲染是同步的
- 如果你设备非常慢,你会感觉还算是灵敏的
- 虽然是异步渲染,但是你将会看到完整的渲染,而不是一个组件一行行的渲染出来
- 同样书写组件的方式
注:简单的来说,react的异步渲染其实就是拉长了render的时间,一次跑一点,所以机器性能很差的,会看到react渲染时有稍微的延迟,但是不是卡顿。
17.2 Suspense(悬停)
Suspense主要解决的就是网络IO问题。网络IO问题其实就是我们现在用Redux+saga等等一系列乱七八糟的库来解决的「副作用」问题。
- 引入新的api,可以使得任何state更新暂停,直到条件满足时,再渲染(像async/await)
- 可以在任何一个组件里放置异步获取数据,而不用做多余的设置
- 在网速非常快的时候,可设置整个数据到达Dom,更新完毕以后再渲染
- 在网速非常慢的时候,可设置精确到单个组件的等待、以及更新,然后再渲染
- 会给我们提供 high-level (createFetcher)和 low-level( Placeholder, Loading ) 的 API,可以供给业务代码和一些小组件的书写。
18. React 16 一系列重要变化
- render/纯组件能够return任何数据结构,以及CreatePortal Api
- 新的context api,尝试代替一部分redux的职责
- babel的<>操作符,方便用户返回数组
- 异步渲染/时间切片(time slicing),成倍提高性能
- componentDidCatch,错误边界,框架层面上提高用户debug的能力
- 未来的Suspense,优雅处理异步副作用
19. pureComponent 和 FunctionComponent
19.1 pureComponent
- PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。
- 不需要开发者自己实现shouldComponentUpdate,就可以进行简单的判断来提升性能。
19.2 FunctionComponent
- function component更易于编写阅读和测试
- 代码量更少,上手容易
- 因为没有状态,可以更好的实现容器和表现的分离,可以只负责表现层的逻辑,不用考虑因为复杂的逻辑去改变状态从而带来的麻烦,有利于代码复用。
- react团队提倡使用
20. Immutable.js
20.1 来源
- js中的对象是可变的,因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象会影响到原始对象
- 为了解决这个问题,一般就是使用浅拷贝或者深拷贝来避免被修改,但是这样会造成cpu和内存的浪费
20.2 immutable Data
- 一旦创建,就不能更改的数据,对immutable对象的任何修改或删除添加都会返回一个新的immutable对象
- 实现原理就是持久化数据结构,在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变,同时为了避免深度复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树种的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点则共享。
- 优点
- immutable降低了Mutable带来的复杂度
- 节省内存
- 时间旅行,复制粘贴这些操作做起来非常简单
- 并发安全:传统的并发需要加锁,但是这个数据是天生不可变的,所以并发就不需要了
21. react 数据流管理
react是自上而下的单向组件数据流,容器组件&展示组件(也叫傻瓜组件&聪明组件)是最常用的react组件设计方案,容器组件负责处理复杂的业务逻辑以及数据,展示组件负责处理UI层,通常我们会将展示组件抽出来进行复用或者组件库的封装,容器组件自身通过state来管理状态,setState更新状态,从而更新UI,通过props将自身的state传递给展示组件实现通信。
21.1 props 和 state
当业务需求不复杂,页面较简单时我们常用的数据流处理方式,仅用react自身提供的props和state来管理
21.2 context
react V16.3版本以后,新版本context解决了之前的问题,可以轻松实现跨组件通信、状态同步以及状态共享。但依然存在一个问题,context也是将底部子组件的状态控制交给到了顶级组件,但是顶级组件状态更新的时候一定会触发所有子组件的re-render,那么也会带来损耗。
21.3 redux
-
状态持久化:global store可以保证组件就算销毁了也依然保留之前状态
-
状态可回溯:每个action都会被序列化,Reducer不会修改原有状态,总是返回新状态,方便做状态回溯
-
Functional Programming:使用纯函数,输出完全依赖输入,没有任何副作用
-
中间件:针对异步数据流,提供了类express中间件的模式,社区也出现了一大批优秀的第三方插件,能够更精细地控制数据的流动,对复杂的业务场景起到了缓冲作用
22. props 和 state 的区别
props:
- props:函数组件的props就是函数的入参组件
- 类组件:this.props包含被该组件调用或者定义的props
state:
- 组件中的state包含了随时可能发生变化的数据。
- state是由用户定义,是一个普通的JavaScript对象
区别:
- props是传递给组件的(类似于函数的形参),而state是在组件内部被组件自己管理的(类似于在一个函数内声明的变量)
- props是不可以被修改的,所有的react组件都必须像纯函数一样保护他们的props不被修改
- state是在组件中创建,一般是在constructor中初始化state
- state是多变的,可被修改的。每次setState都是异步更新的
23. React Context
23.1 定义
Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。
23.2 API
**React.createContext:**创建一个上下文的容器(组件), defaultValue可以设置共享的默认数据
const {Provider, Consumer} = React.createContext(defaultValue);
Provider(生产者): 用于生产共享数据的地方。value:放置共享的数据。
<Provider value={/*共享的数据*/}>
/*里面可以渲染对应的内容*/
</Provider>
Consumer(消费者):这个可以理解为消费者。 是专门消费供应商(Provider 上面提到的)产生数据。Consumer需要嵌套在生产者下面。才能通过回调的方式拿到共享的数据源。当然也可以单独使用,那就只能消费到上文提到的defaultValue。
<Consumer>
{value => /*根据上下文 进行渲染相应内容*/}
</Consumer>
React-router
1. 路由的基本概念
react-router
提供了以下三大组件
Router
是所有路由组件共用的底层接口组件,它是路由规则制定的最外层容器Route
路由规则匹配,并显示当前的规则对应的组件Link
路由跳转的组件
<BrowserRouter>
浏览器的路由组件
<HashRouter>
URL格式为Hash路由组件
<MemoryRouter>
内存路由组件
<NativeRouter>
Native的路由组件
<StaticRouter>
地址不改变的静态路由组件
![img](https://upload-images.jianshu.io/upload_images/4393631-42e115b23670916b.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp)
如果说我们的应用程序是一座小城的话,那么Route就是一座座带有门牌号的建筑物,而Link就代表了到某个建筑物的路线。有了路线和目的地,那么就缺一位老司机了,没错Router就是这个老司机。
import React, { Component } from 'react';
import { HashRouter as Router, Link, Route } from 'react-router-dom';
import './App.css';
const Home = () => (
<div>
<h2>Home</h2>
</div>
)
const About = () => (
<div>
<h2>About</h2>
</div>
)
const Product = () => (
<div>
<h2>Product</h2>
</div>
)
class App extends Component {
render() {
return (
<Router>
<div className="App">
<Link to="/">Home</Link>
<Link to="/About">About</Link>
<Link to="/Product">Product</Link>
<hr />
<Route path="/" exact component={Home}></Route>
<Route path="/about" component={About}></Route>
<Route path="/product" component={Product}></Route>
</div>
</Router>
);
}
}
export default App;
2. BrowserRouter
BrowserRouter
主要使用在浏览器中,利用 HTML5 的history API来同步 URL 和 UI 的变化。点击了程序中的一个链接之后,BrowserRouter
就会找出与这个URL
匹配的Route
,并将他们对应的组件渲染出来。
3. HashRouter
HashRouter
使用 URL 的 hash (例如:window.location.hash
) 来保持 UI 和 URL 的同步。
4. Link
Link就像是一个个的路牌,为我们指明组件的位置。Link使用声明式的方式为应用程序提供导航功能,定义的Link最终会被渲染成一个a标签。Link使用to这个属性来指明目标组件的路径,可以直接使用一个字符串,也可以传入一个对象。
4.1 <Link>
标签和 <a>
标签的区别
<Link>
是 react-router 里实现路由跳转的链接,一般配合 <Route>
使用,react-router 接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link>
的“跳转”行为只会触发相匹配的 <Route>
对应的页面内容更新,而不会刷新整个页面。
<a>
标签就是普通的超链接了,用于从当前页面跳转到 href 指向的另一个页面(非锚点情况)。跳转时,页面重新渲染,性能开销大,用户体验差。
5. Route 组件
Route
应该是 react-route
中最重要的组件,它的作用是当 location
与 Route
的 path
匹配时渲染 Route
中的Component
。如果有多个Route
匹配,那么这些Route
的Component
都会被渲染。
exact
属性,作用是要求 location
与 Route
的 path
绝对匹配。
6. Redirect 组件
当组件被渲染时,location
会被重写为 Redirect
的 to
指定的新 location
。它的一个用途是登录重定向。
7. Switch 组件
当有一堆 <Route>
时,<Switch>
会仅仅渲染一个路由。
Redux
1.Redux 工作流程
- View 在 redux 中会派发 action 方法
- action 通过 store 的 dispatch 方法会派发给 store
- store 接收 action,连同之前的 state,一起传递给 reducer
- reducer 返回新的数据给 store
- store 去改变自己的 state
1.1 action
export const addNameCreater = (name) => {
return {
type: ADDNAME,
data: name
}
}
- 是行为的抽象
- 是普通 JS 对象
- 一般由方法生成
- 必须有一个 type
1.2 reducer
function addName(state = 'initRedux', action) {
switch (action.type) {
case ADDNAME:
return action.data
default:
return state
}
}
- 是响应的抽象
- 纯函数,只要是同样的输入,必定得到同样的输出
- 不得改写参数(形参)
- 不能调用系统 I/O 的API
- 不能调用 Date.now() 或 Math.random() 等不纯的方法,每次结果会不一致
- 传入旧状态和 action
- 返回新状态
- 不能修改 state
1.3 store
import { createStore, applyMiddleware } from 'redux'
import { finalReducer } from './reducers'
import thunk from 'redux-thunk'
//生成store对象
//内部会第一次调用reducer函数,得到初始state
const store = createStore(finalReducer, applyMiddleware(thunk));
export default store
- action 作用于 store
- reducer 根据 store 响应
- store 是唯一的
- store 包括了完整的 state
- state 完全可预测
1.4 action-type
可有可无,根据自己需求。操作字符串很容易出错。常量会减少出错的次数,方便调试
export const ADDNAME = 'ADDNAME'
1.5 项目中使用 redux
参考文章:https://jspang.com/detailed?id=68#toc319
2. Redux 中间件
什么是中间件:
中间件是 dispatch 一个 action 到触发 reducer 之间做的一个额外操作,通常使用中间件 Middleware 来进行日志记录、创建崩溃报告、调用异步接口、路由、或者改变dispatch
2.1 redux-thunk
redux-thunk 是 redux 的中间件,它可以 redux 支持函数类型 action 的发送
- 配置中间件,在store的创建中配置
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk'
// 设置调试工具
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
// 设置中间件
const enhancer = composeEnhancers(
applyMiddleware(thunk)
);
const store = createStore(reducer, enhancer);
export default store;
- 添加一个返回函数的actionCreator,将异步请求逻辑放到这里
/**
发送get请求,并生成相应action,更新store的函数
@param url {string} 请求地址
@param func {function} 真正需要生成的action对应的actionCreator
@return {function}
*/
// dispatch为自动接收的store.dispatch函数
export const getHttpAction = (url, func) => (dispatch) => {
axios.get(url).then(function(res){
const action = func(res.data)
dispatch(action)
})
}
- 生成action,并发送action
componentDidMount(){
var action = getHttpAction('/getData', getInitTodoItemAction)
// 发送函数类型的action时,该action的函数体会自动执行
store.dispatch(action)
}
2.2 redux-saga
redux-saga可以捕获action,然后执行一个函数,那么可以把异步请求代码放在这个函数中
- 配置中间件
import {createStore, applyMiddleware, compose} from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga'
import TodoListSaga from './sagas'
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const sagaMiddleware = createSagaMiddleware()
const enhancer = composeEnhancers(
applyMiddleware(sagaMiddleware)
);
const store = createStore(reducer, enhancer);
sagaMiddleware.run(TodoListSaga)
export default store;
- 将异步请求放在在sagas.js文件中
import {takeEvery, put} from 'redux-saga/effects'
import {initTodoList} from './actionCreator'
import {GET_INIT_ITEM} from './actionTypes'
import axios from 'axios'
function* func(){
try{
// 可以获取异步返回数据
const res = yield axios.get('/getData')
const action = initTodoList(res.data)
// 将action发送到reducer
yield put(action)
}catch(e){
console.log('网络请求失败')
}
}
function* mySaga(){
// 自动捕获GET_INIT_ITEM类型的action,并执行func
yield takeEvery(GET_INIT_ITEM, func)
}
export default mySaga
- 发送 action
componentDidMount(){
const action = getInitTodoItemAction()
store.dispatch(action)
}
3. Redux 设计和使用三大原则
- state 以单一对象存储在 store 对象中
- state 只读
- 使用纯函数 reducer 执行 state 更新
4. Redux状态管理器和变量挂载到window中有什么区别
- 状态更改可回溯,数据多的时候可以很清晰的知道改动在哪里发生
- 状态管理,状态分发,状态同步
- 数据可控,数据响应
5. Redux数据回溯设计思路
6. connect原理
react-redux用于连接react组件及redux,方便开发者使用redux管理状态。其中connect方法是关键,用法如下:
connect([mapStateToProps], [mapDispatchToProps])(component)
看 connect
使用方法就知道是高阶组件,接收参数为 mapStateToProps
和 mapDispatchToProps
俩个方法,返回的函数接收参数是组件,从而返回一个新的组件。
React16 / 15 之间的区别
-
React16生命周期弃用了
componentWillMount
、componentWillReceivePorps
,componentWillUpdate
-
新增了
get DerivedStateFromProps
、getSnapshotBeforeUpdate
来代替弃用的三个钩子函数(componentWillMount
、componentWillReceivePorps
,componentWillUpdate
) -
React16并没有删除这三个钩子函数,但是不能和新增的两个钩子函数(
getDerivedStateFromProps
、getSnapshotBeforeUpdate
)混用。注意:React17将会删除componentWillMount
、componentWillReceivePorps
,componentWillUpdate
-
新增了对错误处理的钩子函数(
componentDidCatch
)class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // Display fallback UI this.setState({ hasError: true }); // You can also log the error to an error reporting service logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; } }
使用的时候,这样处理:
render(){ return ( <ErrorBoundary><User /></ErrorBoundary> ) }
-
返回值支持
fragments and strings
//返回字符串的方式 class App extends Component{ render(){ return 'hello react!' } } //返回数组的方式 class App1 extends Component{ render(){ return [ <li key='A'>item1</li>, <li key='B'>item2</li>, <li key='C'>item3</li>, ] } }
-
Portals
,把一个组件挂载到界面的任意DOM上。使用场景:给一个界面添加Modal层。
render() { return ReactDOM.createPortal( this.props.children, domNode, ); }
-
setState
传入null
时不会再触发更新selectCity(e){ const newValue = e.target.value; this.setState((state)=>{ if(state.city===newValue){ return null; } return {city:newValue} }) )
-
支持自定义
DOM
属性在之前的版本中,
React
会忽略无法识别的HTML
和SVG
属性,自定义属性只能通过data-*
形式添加,现在它会把这些属性直接传递给DOM
(这个改动让React
可以去掉属性白名单,从而减少了文件大小),不过有些写法仍然是无效的。如DOM
传递的自定义属性是函数类型或event handler
时,依然会被React
忽略。 -
基于流模式的服务端渲染
React16
的SSR
被完全重写,新的实现非常快,接近3倍性能于React15
,现在提供一种流模式streaming
,可以更快地把渲染的字节发送到客户端。另外,React 16
在hydrating
(注:指在客户端基于服务器返回的HTML
再次重新渲染)方面也表现的更好,React 16
不再要求客户端的初始渲染完全和服务器返回的渲染结果一致,而是尽量重用已经存在的DOM元素。不会再有checksum(标记验证)并对不一致发出警告。一般来说,在服务器和客户端渲染不同的内容是不建议的,但这样做在某些情况下也是有用的(比如,生成timestamp) -
新的打包策略
新的打包策略中去掉了
process.env
检查。React 16
的体积比上个版本减小了32%(30% post-gzip),文件尺寸的减小一部分要归功于打包方法的改变。 React 16采用了新的核心架构React Fiber
。官方解释是“React Fiber是对核心算法的一次重新实现”。
文章中部分代码及文字来源于网络,特此声明。
文章若有错误,望指出