不折腾的前端,和咸鱼有什么区别
| 目录 |
|---|
| 一 目录 |
| 二 前言 |
| 三 知识点 |
| 四 前端历史演进 |
| 五 React 相比原生的好处 |
| 六 React 和 Vue 比对 |
| 6.1 相同之处 |
| 6.2 不同之处 |
| 七 React Fiber |
| 八 React 生命周期 |
| 8.1 版本 之前 |
| 8.2 版本 之后 |
| 8.2.1 挂载阶段 |
| 8.2.2 更新阶段 |
| 8.2.3 卸载阶段 |
| 九 setState |
| 9.1 调用 setState 之后发生了什么? |
| 9.2 setState 是同步还是异步? |
| 十 React this 问题 |
| 十一 受控组件和非受控组件 |
| 十二 组件通讯 |
| 十三 Redux |
| 十四 Mixin、HOC 和 Hook |
| 14.1 Mixin |
| 14.2 高阶组件(HOC) |
| 14.3 Hook |
| 十五 性能优化 |
| 十六 参看文献 |
| 16.1 面试知识点 |
| 16.2 系统 |
| 16.3 React 和 Vue 比对 |
| 16.4 生命周期 |
| 16.5 受控组件和非受控组件 |
| 16.6 Diff 和 虚拟 DOM |
| 16.7 React 源码 |
| 16.8 React Mixin |
| 16.9 React Hoc |
| 16.10 React Hooks |
| 16.11 React Fiber |
| 16.12 服务端渲染(SSR) |
| 16.13 性能优化 |
| 16.14 其他 |
React 是现如今流行的前端框架,也是很多大厂面试必备。
React 与 Vue 虽有不同,但同样作为一款 MV* 框架,虽然实现可能不一样,但在一些理念上还是有相似的,例如数据驱动、组件化、虚拟 DOM 等。
当然,还有一些问题,可能最近都没法搞清楚的了,毕竟罗马不是一天能建成的:
这些往往要更深入挖掘方可得知自己的结论,任重道远,不停歇。
constructor、getDerivedStateFromProps、render、componentDidMountgetDerivedStateFromProps、shouldComponentUpdate、render、getSnapshotBeforeUpdate、componentDidUpdatecomponentWillUnmountsetState
bind 修正bind 和箭头函数的区别value 和 defaultValuepropsContextReduxRedux:Redux、React-Redux 以及 Redux-Saga 工作流Mixin、HOC 和 Hookprerender-spa-pluginreact-placeholderhtml-webpack-pluginTree ShakingSplitChunkPluginCode Splittingreact-lazyload通过 Ajax 从后端获取数据,然后通过 jQuery 生成 DOM 结果更新到页面中。
但是随着业务发展,项目越来越复杂,交互性越来越强,往往用户在某个时刻可能操作好几块内容,从而 DOM 的操作越来越频繁,页面性能逐步降低,用户也不满意这样卡慢的现状了。
这时候有了 MVVM,双向数据绑定让数据在修改的时候同步 DOM 的更新,反之亦可。
这个设定大大降低手动维护 DOM 的成本,而 MVVM 为 React 的特性之一,虽然 React 属于单项数据流,需要我们手动实现双向数据绑定。
光靠绑定是不够的,这样没法解决频繁操作 DOM 的问题。
所以 React 内部实现了一套虚拟 DOM 的更新,它将真实 DOM 在 JS 中做一套缓存,每次有数据更新的时候,先内部通过 Diff 算法进行比对,然后收集一箩筐更新后,才对 DOM 进行更新,这样就大大降低了 DOM 的操作次数。
那么,Diff 怎么运作呢?
Diff 获取虚拟 DOM 节点变更的 4 种情况比较:节点类型变了、节点类型一样,仅仅属性或者属性值变了、文本变了、增加、删除或者移动了子节点。
React 不同于 Vue,可以通过 v-model 的形式,让用户的操作和 JavaScript 存储的数据同步更新,它需要通过 setState 来更新组件内容。
但是,如果想通过一个组件来渲染它兄弟组件,React 一开始在这块做得并不是那么好,所以就需要引入一个状态管理中心,来帮助我们管理状态(state),因而就有了 Redux。
在 Redux 中,当 state 有变化的时候,依赖这个 state 的组件就会重新渲染,这样就解决了组件间数据传递的问题。
Redux 有个问题,就是修改某个 state 的时候,需要经过 action.js、types.js、reducers.js 这一系列文件,这样子 Redux 的数据流虽然非常正规,但是写起来复杂啊。
所以,社区又出现了另一套解决方案,也就是 Mobx。
Mobx 推崇代码简约移动,只需要定义一个可贯彻的对象,然后在哪个组件中使用到了这个可观察对象,并且这个对象的数据有更改,那就会重新渲染,这使得 Mobx 开发项目的时候可以简单快速地完成很多功能。
但是 Mobx 也有缺点,就是数据流太过随意,出了 Bug 不好定位。
所以,针对于小项目来说,社区推荐使用 MobX,对大项目推荐使用 Redux。
本段文本参考的内容已记录在参考文献中
Webpack + Babel 去搭建脚手架。Vue-router 和 Vuex,而 React 有 React-router 和 React-Redux。HTML 的模板进行渲染,而 React 推荐 JSX 的书写方式。v-model 绑定的数据,用户改变输入值后对应的值也相应改变。而 React 需要通过 setState 进行设置变化。Diff 队列保存需要更新的 DOM,得到 patch 树,再统一批量更新 DOM。实话实说是看别人说的异同,担心有 “小伙伴” 有自己观点,然后在这里被喷,jsliang 不敢持有任何自己的观点,但是面试我总是要这么回答面试官的 /滑稽
React 的核心流程可以分为两个部分:
reconciliation (调度算法,也可称为 render):
state 与 propsdiff 算法,获取 VDOM changecommit
为什么需要 Fiber?
随着应用变得越来越庞大,整个更新渲染的过程开始变得吃力,大量的组件渲染会导致主进程长时间被占用,导致一些动画或高频操作出现卡顿和掉帧的情况。
而关键点,便是 同步阻塞。
在之前的调度算法中,React 需要实例化每个类组件,生成一颗组件树,使用 同步递归 的方式进行遍历渲染,而这个过程最大的问题就是无法 暂停和恢复。
所以,为了解决这个问题(同步阻塞),通常有两种方法: 异步 与 任务分割。
而 React Fiber 便是为了实现任务分割而诞生的。
React Fiber 简述:
stack reconciler 重构成新版的 fiber reconciler,变成了具有链表和指针的 单链表树遍历算法。通过指针映射,每个单元都记录着遍历当下的上一步与下一步,从而使遍历变得可以被暂停和重启。React Fiber 核心:
React 逐渐废弃的生命周期方法:
componentWillMountcomponentWillReceivePropscomponentWillUpdate

constructor:构造函数,最先被执行,通常在构造函数中初始化 state 对象或者给自定义方法绑定 thisgetDerivedStateFromProps:static getDerivedStateFromProps(nextProps, prevState),这是个静态方法,当我们接收到新的属性想去修改我们 state,可以使用 getDerivedStateFromProps。render:render 函数是个纯函数,只返回需要渲染的东西,不应该包含其他的业务逻辑,可以返回原生 DOM、React 组件、Fragment、Portals、字符串和数字等内容。componentDidMount:组件装载之后调用,此时我们可以获取到 DOM 节点并操作,比如对 Canvas、SVG 等操作。服务器请求、订阅都可以写这个里面,但是记得在 componentWillUnmount 中取消订阅。React 的接口请求是放在 componentDidMount 里面比较合适,旧版本有人放在 componentWillMount 里面,从而导致多次请求,现在 componentWillMount 不推荐使用了,所以转 componentDidMount 就非常科学了。
存在以下问题:
getDerivedStateFromProps 是静态的?当它设置为静态函数,表明这个函数不能通过 this 访问到 class 的属性,也并不推荐直接访问属性。
setState?可以在 componentDidMount 和 componentDidUpdate 中使用,此时 DOM 已经稳定下来了,可以进行数据的操作了。
getDerivedStateFromProps:此方法在更新阶段也会被调用。shouldComponentUpdate:shouldComponentUpdate(nextProps, nextState),有两个参数,表示新的属性和变化之后的 state,返回一个布尔值。如果是 true 表示会触发重新渲染,false 表示不会触发重新渲染,默认返回 true。可以利用这个生命周期来优化 React 程序性能。render:同挂载阶段 render。getSnapshotBeforeUpdate:getSnapshotBeforeUpdate(prevProps, prevState),这个方法会在 render 之后,componentDidUpdate 之前调用,有两个参数,表示之前属性和之前的 state。这个函数有一个返回值,会作为第三个参数传给 componentDidUpdate,如果不需要返回值,可以返回 null,这个方法必须和 componentDidUpdate 配合使用。componentDidUpdate:componentDidUpdate(prevProps, prevState, snapshot),在 getSnapshotBeforeUpdateprops,之前的 state,以及 snapshot。参数 snapshot 是 getSnapshotBeforeUpdate 返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或者计算过程迁移到 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或者更新状态。componentWillUnmount:当组件被卸载或者销毁时会被调用,在这里清除定时器,或者取消网络请求,用来清理无效的 DOM 元素等垃圾回收工作。setState 是 React 中用于修改状态,更新视图的方法。
在代码中调用 setState 之后,React 会将传入的参数对象与组件当前的状态合并,触发所谓的调和过程(Reconciliation)。
经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。
在 React 得到元素树之后,React 会自动计算新树和老树之间的节点差异,然后根据差异对界面进行最小化重新渲染。
在差异计算算法(Diff)中,React 能够相对精确地知道哪些位置发生了改变以及英国如何改变,保证了按需更新,而不是全部重新渲染。
简单来说:
Diff)回答:有时候同步,有时候异步。
setState 在合成事件和钩子函数中是异步的,在原生事件和 setTimeout 是同步的。setState 的异步,并不是说内部由异步代码实现,它本身执行的过程和代码是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,从而形成了所谓的异步。setState 可以通过第二个参数 setState(partialState, callback),在回调方法中拿到更新后的结果。在 React 中有几种方法可以修正 this 的指向,这里例举 4 种方法:
import React, { Component } from 'react'
class App extends Component {
constructor (props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
console.log('jsliang 2020');
}
handleClick2 = () => {
console.log('jsliang 2021');
}
render () {
// 四种绑定方法
return (
<div className='App'>
{/* 方法一:通过 constructor 中进行 bind 绑定 */}
<button onClick={this.handleClick}>btn 1</button>
{/* 方法二:在里边绑定 this */}
<button onClick={this.handleClick.bind(this)}>btn 2</button>
{/* 方法三:通过箭头函数返回事件 */}
<button onClick={() => this.handleClick()}>btn 3</button>
{/* 方法四:让方法变成箭头函数 */}
<button onClick={this.handleClick2}>btn 4</button>
{/* 额外:直接调用不需要绑定 this */}
{this.handleClick()}
</div>
)
}
}
export default App;那么,使用 bind 和箭头函数有什么区别吗?
箭头函数除了代码少,与普通函数最大的不同就是:this 是由声明该函数时候定义的,一般是隐性定义为声明该函数时的作用域 this。
通过 bind 的话,相当于:Foo.prototype.a = function() {},是通过原型链的一个指正绑定。
而通过箭头函数的话,就相当于:
class Foo {
constructor() {
this.a = () => {};
}
}在 Ant Design 中,对 Input 输入框进行操作,如果是改变 defaultValue 会发现毫无作用。
这是因为 React 的 form 表单组件中的 defaultValue 一经传递值后,后续改变 defaultValue 都将不起作用,被忽略了。
具体来说这是一种 React 非受控组件,其状态是在 input 的 React 内部控制,不受调用者控制。
所以受控组件就是可以被 React 状态控制的组件。双向数据绑定就是受控组件,你可以为 form 中某个输入框添加 value 属性,然后控制它的一个改变。而非受控组件就是没有添加 value 属性的组件,你并不能对它的固定值进行操作。
props 方式,向子组件进行通讯。props 中传递方法,然后子组件调用这个方法,将自身需要传递的信息,传递到父组件的作用域中。Context,或者 Redux 进行数据通讯。网上有挺多关于 Redux、React-Redux、Redux-Saga 的使用,这里就不废话介绍了,还是讲讲 jsliang 在工作中的一个使用吧。
工作目录
- 某个页面文件夹
- View.jsx 当前页面主入口
- Child.jsx 子组件
- Brother.jsx 兄弟组件
- action.js 动作
- types.js 类型
- saga.js 调用接口
- reducers.js 处理数据正常的一个工作目录如上所示,我们工作中是怎么个使用方式呢?
首先,在 View.jsx 中通过 React-Redux 连接 React 和 Redux
然后,假设现在 Child.jsx 需要调用接口(异步处理),那么会:
action.js 中定义这个方法,会传递什么参数。types.js 是辅助 action.js 的一个内容,为了防止方法体的重复,我们会在 types.js 中定义大写的 action 名字。View.jsx 中通过 dispatch 触发方法,例如 dispatch(getPage(page, perPage))。reducers.js 中和 sage.js 中都能监听到这个方法,但是我们是在 sage.js 中调用接口并处理数据。sage.js 中的传递给 reducers.js 中,让它去处理数据。接着,如果 Brother.jsx 只是单纯地想处理数据并在 Child.jsx 中使用,那么我们处理方式是跟上面一样的,只是直接在 reducers.js 中处理,而不需要再在 sage.js 中调用接口而已。
最后,我们再看看 redux 和 react-reduxt 的工作流程加深印象:
Redux

React-Redux

前端发展速度非常之快,页面和组件变得越来越复杂,如何更好的实现 状态逻辑复用 一直都是应用程序中重要的一部分,这直接关系着应用程序的质量以及维护的难易程度。
Mixin、HOC 和 Hook 是 React 采用的 3 种 状态逻辑复用 的技术,Mixin 已被抛弃,HOC 正当壮年,Hook 初露锋芒,掌握它迭代因素和规律非常重要。
Mixin(混入)是一种通过扩展收集功能的方式,它本质上是将一个对象的属性拷贝到另一个对象上面去。
不过你可以拷贝任意多个对象的任意个方法到一个新对象上去,这是继承所不能实现的。
它的出现主要就是为了解决代码复用问题。

但是,它会带来一些危害:
Mixin 相互依赖、相互耦合,不利于代码维护Mixin 中的方法可能会互相冲突Mixin 非常多时,组件是可以感知到的,甚至还要为其做相关处理,这样给代码造成滚雪球式的复杂性。基于 Mixin 的问题,React 推出对装饰模式的一种实现:高阶组件(HOC)。
高阶组件接收一个组件作为参数,并返回一个新的组件。
高阶组件(
HOC)是 React 中的高级技术,用来重用组件逻辑。但高阶组件本身并不是 React API。它只是一种模式,这种模式是由 React 自身的组合性质必然产生的。
function visible(WrappedComponent) {
return class extends Component {
render() {
const { visible, ...props } = this.props;
if (visible === false) return null;
return <WrappedComponent {...props} />;
}
}
}高阶组件可以应用于 日志打点、可用权限控制、双向绑定、表单校验等。
高阶组件解决了 Mixin 带来的问题:
但是,有光的地方总有暗,高阶组件也存在一些缺陷:
HOC 需要在原组件上进行包裹或者嵌套,如果大量使用 HOC,将会产生非常多的嵌套,这让调试变得非常困难。HOC 可以劫持 props,在不遵守约定的情况下也可能造成冲突。Hook 是 React v16.7.0-alpha 中加入的新特性。它可以让你在 class 以外使用 state 和其他 React 特性。
使用 Hook,你可以在将含有 state 的逻辑从组件中抽象出来,这将可以让这些逻辑容易被测试。
同时,Hook 可以帮助你在不重写组件结构的情况下复用这些逻辑。
所以,它也可以作为一种实现状态逻辑复用的方案。
Hook 使用带来的好处:
Hook 和 Mixin 在用法上有一定的相似之处,但是 Mixin 引入的逻辑和状态是可以相互覆盖的,而多个 Hook 之间互不影响,这让我们不需要在把一部分精力放在防止避免逻辑复用的冲突上。HOC 的情况下让我们的代码变得嵌套层级非常深,使用 Hook,我们可以实现扁平式的状态逻辑复用,而避免了大量的组件嵌套。class 组件构建我们的程序时,他们各自拥有自己的状态,业务逻辑的复杂使这些组件变得越来越庞大,各个生命周期中会调用越来越多的逻辑,越来越难以维护。使用 Hook,可以让你更大限度的将公用逻辑抽离,将一个组件分割成更小的函数,而不是强制基于生命周期方法进行分割。class 可能需要掌握更多的知识,需要注意的点也越多,比如 this 指向、绑定事件等等。另外,计算机理解一个函数比理解一个 class 更快。Hooks 让你可以在 class 之外使用更多 React 的新特性。<div id="root"> SVG </div>,也可以使用插件 prerender-spa-plugin 插件进行首屏渲染。html-webpack-plugin 插件自动插入 loading,这样切换的时候,就不需要在每个页面都写一套 loading。Tree Shaking 来减少一些代码。SplitChunkPlugin 自动拆分业务基础库,减少大文件的存在。Code Splitting 来懒加载代码,提高用户的加载体验。例如通过 React Loadable 来将组件改写成支持动态 import 的形式。react-lazyload 这种成熟组件来进行懒加载的支持。react-placeholder 可以解决这种情况。本系列有 67 篇参考文献。
2019:
2018:
2017:
2017:
2020:
2017:
最新:
2019:
2018:
2017:
2019:
2016:
2019:
2018:
2017:
2015:
2018:
2017:
2015:
2015:
2019:
2017:
2019:
2018:
2018:
2019:
2018:
2017:
2019:
2018:
2017:
2015:
jsliang 的文档库由 梁峻荣 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议 进行许可。
基于 https://github.com/LiangJunrong/document-library 上的作品创作。
本许可协议授权之外的使用权限可以从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处获得。