react调用api等待返回结果_React16常用api解析以及原理剖析

ppt 预览:ru23.github.io/react-ppt/

  • 空格键翻页
  • 左右键切换页面,上下键翻章节
  • esc 章节预览

目录

  1. react 生命周期
  2. react 事件机制
  3. react.Component 如何实现组件化以及高阶组件的应用
  4. setState 异步队列数据管理
  5. react Fiber 架构分析
  6. react hooks
  7. dom 的 diff 算法
  8. snabbdom 源码,是怎样实现精简的 Virtual DOM 的
  9. redux

react 生命周期

目前 react 16.8 +的生命周期分为三个阶段,分别是挂载阶段、更新阶段、卸载阶段

  • 挂载阶段: constructor(props): 实例化。
  • static getDeriverdStateFromProps 从 props 中获取 state。
  • render 渲染。
  • componentDidMount: 完成挂载。
  • 更新阶段: static getDeriverdStateFromProps 从 props 中获取 state。
  • shouldComponentUpdate 判断是否需要重绘。
  • render 渲染。
  • getShapshotBeforeUpdate 获取快照。
  • componentDidUpdate 渲染完成后回调。
  • 卸载阶段: componentWillUnmount 即将卸载。
  • 错误处理: static getDerivedStateFromError 从错误中获取 state。
  • componentDidCatch 捕获错误并进行处理。
class ExampleComponent extends react.Component { // 构造函数,最先被执行,我们通常在构造函数里初始化state对象或者给自定义方法绑定this constructor() {} //getDerivedStateFromProps(nextProps, prevState)用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用 // 这是个静态方法,当我们接收到新的属性想去修改我们state,可以使用getDerivedStateFromProps static getDerivedStateFromProps(nextProps, prevState) { // 新的钩子 getDerivedStateFromProps() 更加纯粹, 它做的事情是将新传进来的属性和当前的状态值进行对比, 若不一致则更新当前的状态。 if (nextProps.riderId !== prevState.riderId) { return { riderId: nextProps.riderId } } // 返回 null 则表示 state 不用作更新 return null } // shouldComponentUpdate(nextProps, nextState),有两个参数nextProps和nextState,表示新的属性和变化之后的state,返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true,我们通常利用此生命周期来优化react程序性能 shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id } // 组件挂载后调用 // 可以在该函数中进行请求或者订阅 componentDidMount() {} // getSnapshotBeforeUpdate(prevProps, prevState):这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用 getSnapshotBeforeUpdate() {} // 组件即将销毁 // 可以在此处移除订阅,定时器等等 componentWillUnmount() {} // 组件销毁后调用 componentDidUnMount() {} // componentDidUpdate(prevProps, prevState, snapshot):该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。 componentDidUpdate() {} // 渲染组件函数 render() {} // 以下函数不建议使用 UNSAFE_componentWillMount() {} UNSAFE_componentWillUpdate(nextProps, nextState) {} UNSAFE_componentWillReceiveProps(nextProps) {}}复制代码

react 版本 17 将弃用几个类组件 API 生命周期:componentWillMount,componentWillReceiveProps和componentWillUpdate。

react 事件机制

简单的理解 react 如何处理事件的,React 在组件加载(mount)和更新(update)时,将事件通过 addEventListener 统一注册到 document 上,然后会有一个事件池存储了所有的事件,当事件触发的时候,通过 dispatchEvent 进行事件分发。

引用新手学习 react 迷惑的点(二)

  • react 里面绑定事件的方式和在 HTML 中绑定事件类似,使用驼峰式命名指定要绑定的 onClick 属性为组件定义的一个方法{this.handleClick.bind(this)}。
  • 由于类的方法默认不会绑定 this,因此在调用的时候如果忘记绑定,this 的值将会是 undefined。 通常如果不是直接调用,应该为方法绑定 this,将事件函数上下文绑定要组件实例上。

绑定事件的四种方式

class Button extends react.Component { constructor(props) { super(props) this.handleClick1 = this.handleClick1.bind(this) } //方式1:在构造函数中使用bind绑定this,官方推荐的绑定方式,也是性能最好的方式 handleClick1() { console.log('this is:', this) } //方式2:在调用的时候使用bind绑定this handleClick2() { console.log('this is:', this) } //方式3:在调用的时候使用箭头函数绑定this // 方式2和方式3会有性能影响并且当方法作为属性传递给子组件的时候会引起重渲问题 handleClick3() { console.log('this is:', this) } //方式4:使用属性初始化器语法绑定this,需要babel转义 handleClick4 = () => { console.log('this is:', this) } render() { return ( 
Click me Click me this.handleClick3}>Click me Click me
) }}复制代码

为什么直接调用方法会报错

class Foo extends React.Component { handleClick() { this.setState({ xxx: aaa }) } render() { return Click me }}复制代码

会被 babel 转化成

React.createElement( 'button', { onClick: this.handleClick }, 'Click me')复制代码

“合成事件”和“原生事件”

react 实现了一个“合成事件”层(synthetic event system),这抹平了各个浏览器的事件兼容性问题。所有事件均注册到了元素的最顶层-document 上,“合成事件”会以事件委托(event delegation)的方式绑定到组件最上层,并且在组件卸载(unmount)的时候自动销毁绑定的事件。

react 组件开发

react 组件化思想

一个 UI 组件的完整模板

import classNames from 'classnames'class Button extends react.Component { //参数传参与校验 static propTypes = { type: PropTypes.oneOf(['success', 'normal']), onClick: PropTypes.func } static defaultProps = { type: 'normal' } handleClick() {} render() { let { className, type, children, ...other } = this.props const classes = classNames( className, 'prefix-button', 'prefix-button-' + type ) return (  this.handleClick}> {children}  ) }}复制代码

函数定义组件(Function Component)

纯展示型的,不需要维护 state 和生命周期,则优先使用 Function Component

  1. 代码更简洁,一看就知道是纯展示型的,没有复杂的业务逻辑
  2. 更好的复用性。只要传入相同结构的 props,就能展示相同的界面,不需要考虑副作用。
  3. 打包体积小,执行效率高
import react from 'react'function MyComponent(props) { let { firstName, lastName } = props return ( 

{[firstName, lastName].join(' ')}

)}复制代码

会被 babel 转义成

return React.createElement( 'div', null, React.createElement('img', { src: 'avatar.png', className: 'profile' }), React.createElement('h3', null, [firstName, lastName].join(' ')))复制代码

createElement 函数对 key 和 ref 等特殊的 props 进行处理,并获取 defaultProps 对默认 props 进行赋值,并且对传入的孩子节点进行处理,最终构造成一个 reactElement 对象(所谓的虚拟 DOM)。 reactDOM.render 将生成好的虚拟 DOM 渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实 DOM。

那么,React.createElement 是在做什么?看下相关部分代码:

var ReactElement = function(type, key, ref, self, source, owner, props) { var element = { // This tag allow us to uniquely identify this as a React Element $$typeof: REACT_ELEMENT_TYPE, // Built-in properties that belong on the element type: type, key: key, ref: ref, props: props, // Record the component responsible for creating this element. _owner: owner } // ... return element}ReactElement.createElement = function(type, config, children) { // ... return ReactElement( type, key, ref, self, source, ReactCurrentOwner.current, props )}复制代码

ES6 class 定义一个纯组件(PureComponent)

组件需要维护 state 或使用生命周期方法,则优先使用 PureComponent

class MyComponent extends react.Component { render() { let { name } = this.props return 

Hello, {name}

}}复制代码

PureComponent

react15.3 中新加了一个类 PureComponent,前身是 PureRenderMixin ,和 Component 基本一样,只不过会在 render 之前帮组件自动执行一次 shallowEqual(浅比较),来决定是否更新组件,浅比较类似于浅复制,只会比较第一层。使用 PureComponent 相当于省去了写 shouldComponentUpdate 函数,当组件更新时,如果组件的 props 和 state:

  • 引用和第一层数据都没发生改变, render 方法就不会触发,这是我们需要达到的效果。
  • 虽然第一层数据没变,但引用变了,就会造成虚拟 DOM 计算的浪费。
  • 第一层数据改变,但引用没变,会造成不渲染,所以需要很小心的操作数据。

使用不可变数据结构 Immutablejs

Immutable.js 是 Facebook 在 2014 年出的持久性数据结构的库,持久性指的是数据一旦创建,就不能再被更改,任何修改或添加删除操作都会返回一个新的 Immutable 对象。可以让我们更容易的去处理缓存、回退、数据变化检测等问题,简化开发。并且提供了大量的类似原生 JS 的方法,还有 Lazy Operation 的特性,完全的函数式编程。

import { Map } from 'immutable'const map1 = Map({ a: { aa: 1 }, b: 2, c: 3 })const map2 = map1.set('b', 50)map1 !== map2 // truemap1.get('b') // 2map2.get('b') // 50map1.get('a') === map2.get('a') // true复制代码

可以看到,修改 map1 的属性返回 map2,他们并不是指向同一存储空间,map1 声明了只有,所有的操作都不会改变它。

ImmutableJS提供了大量的方法去更新、删除、添加数据,极大的方便了我们操纵数据。除此之外,还提供了原生类型与 ImmutableJS 类型判断与转换方法:

import { fromJS, isImmutable } from 'immutable'const obj = fromJS({ a: 'test', b: [1, 2, 4]}) // 支持混合类型isImmutable(obj) // trueobj.size() // 2const obj1 = obj.toJS() // 转换成原生 `js` 类型复制代码

ImmutableJS 最大的两个特性就是: immutable data structures(持久性数据结构)与 structural sharing(结构共享),持久性数据结构保证数据一旦创建就不能修改,使用旧数据创建新数据时,旧数据也不会改变,不会像原生 js 那样新数据的操作会影响旧数据。而结构共享是指没有改变的数据共用一个引用,这样既减少了深拷贝的性能消耗,也减少了内存。

左边是旧值,右边是新值,我需要改变左边红色节点的值,生成的新值改变了红色节点到根节点路径之间的所有节点,也就是所有青色节点的值,旧值没有任何改变,其他使用它的地方并不会受影响,而超过一大半的蓝色节点还是和旧值共享的。在 ImmutableJS 内部,构造了一种特殊的数据结构,把原生的值结合一系列的私有属性,创建成 ImmutableJS 类型,每次改变值,先会通过私有属性的辅助检测,然后改变对应的需要改变的私有属性和真实值,最后生成一个新的值,中间会有很多的优化,所以性能会很高。

高阶组件(higher order component)

高阶组件是一个以组件为参数并返回一个新组件的函数。HOC 运行你重用代码、逻辑和引导抽象。

function visible(WrappedComponent) { return class extends Component { render() { const { visible, ...props } = this.props if (visible === false) return null return  } }}复制代码

上面的代码就是一个 HOC 的简单应用,函数接收一个组件作为参数,并返回一个新组件,新组建可以接收一个 visible props,根据 visible 的值来判断是否渲染 Visible。 最常见的还有 Redux 的 connect 函数。除了简单分享工具库和简单的组合,HOC 最好的方式是共享 react 组件之间的行为。如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC。 下面就是一个简化版的 connect 实现:

export const connect = ( mapStateToProps, mapDispatchToProps) => WrappedComponent => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor() { super() this.state = { allProps: {} } } componentWillMount() { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps() { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props) : {} let dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch, this.props) : {} this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render() { return  } } return Connect}复制代码

代码非常清晰,connect 函数其实就做了一件事,将 mapStateToProps 和 mapDispatchToProps 分别解构后传给原组件,这样我们在原组件内就可以直接用 props 获取 state 以及 dispatch 函数了。

高阶组件的应用

某些页面需要记录用户行为,性能指标等等,通过高阶组件做这些事情可以省去很多重复代码。

日志打点

function logHoc(WrappedComponent) { return class extends Component { componentWillMount() { this.start = Date.now() } componentDidMount() { this.end = Date.now() console.log( `${WrappedComponent.dispalyName} 渲染时间:${this.end - this.start} ms` ) console.log(`${user}进入${WrappedComponent.dispalyName}`) } componentWillUnmount() { console.log(`${user}退出${WrappedComponent.dispalyName}`) } render() { return  } }}复制代码

可用、权限控制

function auth(WrappedComponent) { return class extends Component { render() { const { visible, auth, display = null, ...props } = this.props if (visible === false || (auth && authList.indexOf(auth) === -1)) { return display } return  } }}复制代码

表单校验

基于上面的双向绑定的例子,我们再来一个表单验证器,表单验证器可以包含验证函数以及提示信息,当验证不通过时,展示错误信息:

function validateHoc(WrappedComponent) { return class extends Component { constructor(props) { super(props) this.state = { error: '' } } onChange = event => { const { validator } = this.props if (validator && typeof validator.func === 'function') { if (!validator.func(event.target.value)) { this.setState({ error: validator.msg }) } else { this.setState({ error: '' }) } } } render() { return ( 
{this.state.error || ''}
) } }}复制代码const validatorName = { func: (val) => val && !isNaN(val), msg: '请输入数字'}const validatorPwd = { func: (val) => val && val.length > 6, msg: '密码必须大于6位'}复制代码

HOC 的缺陷

  • HOC 需要在原组件上进行包裹或者嵌套,如果大量使用 HOC,将会产生非常多的嵌套,这让调试变得非常困难。
  • HOC 可以劫持 props,在不遵守约定的情况下也可能造成冲突。

render props

一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术 具有 render prop 的组件接受一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑。

Hello {data.target}

} />复制代码

setState 数据管理

**不要直接更新状态**

// Wrong 此代码不会重新渲染组件,构造函数是唯一能够初始化 this.state 的地方。this.state.comment = 'Hello'// Correct 应当使用 setState():this.setState({ comment: 'Hello' })复制代码

组件生命周期中或者 react 事件绑定中,setState 是通过异步更新的,在延时的回调或者原生事件绑定的回调中调用 setState 不一定是异步的。

  • 多个 setState() 调用合并成一个调用来提高性能。
  • this.props 和 this.state 可能是异步更新的,不应该依靠它们的值来计算下一个状态。
// Wrongthis.setState({ counter: this.state.counter + this.props.increment})// Correctthis.setState((prevState, props) => ({ counter: prevState.counter + props.increment}))复制代码

原生事件绑定不会通过合成事件的方式处理,会进入更新事务的处理流程。`setTimeout` 也一样,在 `setTimeout` 回调执行时已经完成了原更新组件流程,不会放入 `dirtyComponent` 进行异步更新,其结果自然是同步的。

setState 原理

setState 并没有直接操作去渲染,而是执行了一个 updateQueue(异步 updater 队列),

setState( stateChange ) { Object.assign( this.state, stateChange ); //合并接收到的state||stateChange改变的state(setState接收到的参数) renderComponent( this );//调用render渲染组件}复制代码

这种实现,每次调用 setState 都会更新 state 并马上渲染一次(不符合其更新优化机制),所以我们要合并 setState。

具体可以阅读源码 ReactUpdateQueue.js

react 中的事务实现

待完善

ErrorBoundary、Suspense 和 Fragment

Error Boundaries

react 16 提供了一个新的错误捕获钩子 componentDidCatch(error, errorInfo), 它能将子组件生命周期里所抛出的错误捕获, 防止页面全局崩溃。demo componentDidCatch 并不会捕获以下几种错误

  • 事件机制抛出的错误(事件里的错误并不会影响渲染)
  • Error Boundaries 自身抛出的错误
  • 异步产生的错误
  • 服务端渲染

lazy、suspense

lazy 需要跟 Suspence 配合使用,否则会报错。

lazy 实际上是帮助我们实现代码分割 ,类似 webpack 的 splitchunk 的功能。

Suspense 意思是能暂停当前组件的渲染, 当完成某件事以后再继续渲染。简单来说就是减少首屏代码的体积,提升性能。

import react, { lazy, Suspense } from 'react'const OtherComponent = lazy(() => import('./OtherComponent'))function MyComponent() { return ( loading...
}> )}复制代码

一种简单的预加载思路, 可参考 preload

const OtherComponentPromise = import('./OtherComponent')const OtherComponent = react.lazy(() => OtherComponentPromise)复制代码

Fragments(v16.2.0)

Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。

render() { return ( <>  > );}复制代码

react Fiber 架构分析

react-fiber是为了增强动画、布局、移动端手势领域的适用性,最重要的特性是对页面渲染的优化: 允许将渲染方面的工作拆分为多段进行。

react Fiber 架构解决了什么问题

react-fiber 可以为我们提供如下几个功能:

  • 设置渲染任务的优先
  • 采用新的 Diff 算法
  • 采用虚拟栈设计允许当优先级更高的渲染任务和较低优先的任务之间来回切换

Fiber 如何做到异步渲染 Virtual Dom 和 Diff 算法

众所周知,画面每秒钟更新 60 次,页面在人眼中显得流畅,无明显卡顿。每秒 60 次,即 16ms 要更新一次页面,如果更新页面消耗的时间不到 16ms,那么在下一次更新时机来到之前会剩下一点时间执行其他的任务,只要保证及时在 16ms 的间隔下更新界面就完全不会影响到页面的流畅程度。fiber 的核心正是利用了 60 帧原则,实现了一个基于优先级和 requestIdleCallback 的循环任务调度算法。

function fiber(剩余时间) { if (剩余时间 > 任务所需时间) { 做任务 } else { requestIdleCallback(fiber) // requestIdleCallback 是浏览器提供的一个 api,可以让浏览器在空闲的时候执行回调, // 在回调参数中可以获取到当前帧剩余的时间,fiber 利用了这个参数, // 判断当前剩下的时间是否足够继续执行任务, // 如果足够则继续执行,否则暂停任务, // 并调用 requestIdleCallback 通知浏览器空闲的时候继续执行当前的任务 }}复制代码

react hooks

在 react 16.7 之前, react 有两种形式的组件, 有状态组件(类)和无状态组件(函数)。 官方解释: hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 个人理解:让传统的函数组件 function component 有内部状态 state 的函数 function,简单来说就是 hooks 让函数组件有了状态,可以完全替代 class。

接下来梳理 Hooks 中最核心的 2 个 api, useState 和 useEffect

useState

useState 是一个钩子,他可以为函数式组件增加一些状态,并且提供改变这些状态的函数,同时它接收一个参数,这个参数作为状态的默认值。

const [count, setCount] = useState(initialState)复制代码

使用 Hooks 相比之前用 class 的写法最直观的感受是更为简洁

function App() { const [count, setCount] = useState(0) return ( 

You clicked {count} times

setCount(count + 1)}>Click me
)}复制代码

useEffect(fn)

在每次 render 后都会执行这个钩子。可以将它当成是 componentDidMount、componentDidUpdate``、componentWillUnmount 的合集。因此使用 useEffect 比之前优越的地方在于:

可以避免在 componentDidMount、componentDidUpdate 书写重复的代码; 可以将关联逻辑写进一个 useEffect(在以前得写进不同生命周期里);

深入理解 react 原理

react 虚拟 dom 原理剖析

react 组件的渲染流程

使用 react.createElement 或 JSX 编写 react 组件,实际上所有的 JSX 代码最后都会转换成 react.createElement(...),Babel 帮助我们完成了这个转换的过程。

createElement 函数对 key 和 ref 等特殊的 props 进行处理,并获取 defaultProps 对默认 props 进行赋值,并且对传入的孩子节点进行处理,最终构造成一个 reactElement 对象(所谓的虚拟 DOM)。

reactDOM.render 将生成好的虚拟 DOM 渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实 DOM。

虚拟 DOM 的组成

即 reactElementelement 对象,我们的组件最终会被渲染成下面的结构:

`type`:元素的类型,可以是原生 html 类型(字符串),或者自定义组件(函数或 class)`key`:组件的唯一标识,用于 Diff 算法,下面会详细介绍`ref`:用于访问原生 dom 节点`props`:传入组件的 props,chidren 是 props 中的一个属性,它存储了当前组件的孩子节点,可以是数组(多个孩子节点)或对象(只有一个孩子节点)`owner`:当前正在构建的 Component 所属的 Component`self`:(非生产环境)指定当前位于哪个组件实例`_source`:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)复制代码

当组件状态 state 有更改的时候,react 会自动调用组件的 render 方法重新渲染整个组件的 UI。 当然如果真的这样大面积的操作 DOM,性能会是一个很大的问题,所以 react 实现了一个 Virtual DOM,组件 DOM 结构就是映射到这个 Virtual DOM上,react 在这个 Virtual DOM 上实现了一个 diff 算法,当要重新渲染组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以实际上不是真的渲染整个 DOM 树。这个 Virtual DOM 是一个纯粹的 JS 数据结构,所以性能会比原生 DOM 快很多。

react 是如何防止 XSS 的

reactElement 对象还有一个$$typeof属性,它是一个 Symbol 类型的变量Symbol.for('react.element'),当环境不支持 Symbol 时,$$typeof 被赋值为 0xeac7。 这个变量可以防止 XSS。如果你的服务器有一个漏洞,允许用户存储任意 JSON 对象, 而客户端代码需要一个字符串,这可能为你的应用程序带来风险。JSON 中不能存储 Symbol 类型的变量,而 react 渲染时会把没有$$typeof 标识的组件过滤掉。

diff 算法

传统的 diff 算法通过循环递归对节点一次对比,效率很低,算法复杂度达到 O(n^3),其中 n 是树中节点的总数,React 通过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n) 复杂度的问题。

diff 策略:

  1. web ui 中 Dom 节点跨层级的移动操作很少,diff 算法比较新旧节点的时候,比较只会在同层级比较,不会跨层级比较
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
  3. 对于同一层级的一组子节点,他们可以通过唯一 key 进行区分

基于以上三个前提策略,React 分别对 tree diff、component diff 以及 element diff 进行算法优化,事实也证明这三个前提策略是合理且准确的,它保证了整体界面构建的性能。 简单的讲就是:

具体可以参考React 源码剖析系列 - 不可思议的 react diff

  • React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
  • React 通过分层求异的策略,对 tree diff 进行算法优化;
  • React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
  • React 通过设置唯一 key 的策略,对 element diff 进行算法优化;

建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升; 建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

snabbdom 源码,是怎样实现精简的 Virtual DOM 的

待补充

react 性能分析与优化

减少不必要的渲染

在使用 class Component 进行开发的时候,我们可以使用 shouldComponentUpdate 来减少不必要的渲染,那么在使用 react hooks 后,我们如何实现这样的功能呢?

解决方案:React.memo和useMemo 对于这种情况,react 当然也给出了官方的解决方案,就是使用 React.memo 和 useMemo。

React.memo

React.momo 其实并不是一个 hook,它其实等价于 PureComponent,但是它只会对比 props。使用方式如下(用上面的例子):

import React, { useState } from 'react'export const Count = React.memo(props => { const [data, setData] = useState({ count: 0, name: 'cjg', age: 18 }) const handleClick = () => { const { count } = data setData({ ...data, count: count + 1 }) } return count:{data.count}})复制代码

useMemo

useMemo 它的用法其实跟 useEffects 有点像,我们直接看官方给的例子

function Parent({ a, b }) { // Only re-rendered if `a` changes: const child1 = useMemo(() => , [a]) // Only re-rendered if `b` changes: const child2 = useMemo(() => , [b]) return ( <> {child1} {child2} > )}复制代码

从例子可以看出来,它的第二个参数和 useEffect 的第二个参数是一样的,只有在第二个参数数组的值发生变化时,才会触发子组件的更新。

引用React hooks 实践

React 性能分析器

React 16.5 增加了对新的开发者工具 DevTools 性能分析插件的支持。 此插件使用 React 实验性的 Profiler API 来收集有关每个组件渲染的用时信息,以便识别 React 应用程序中的性能瓶颈。 它将与我们即将推出的 time slicing(时间分片) 和 suspense(悬停) 功能完全兼容。

redux

Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store。

State:Store 对象包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。

Action:State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。

Reducer:Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

dispatch:是 View 发出 Action 的唯一方法。

然后我们过下整个工作流程:

首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法。

然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State

State 一旦有变化,Store 就会调用监听函数,来更新 View。

到这儿为止,一次用户交互流程结束。可以看到,在整个流程中数据都是单向流动的,这种方式保证了流程的清晰。

redux 单向数据流架构如何设计

待完善

redux 中间件

待完善

参考:

  1. 深入分析虚拟 DOM 的渲染原理和特性
  2. react 事件机制
  3. 从 Mixin 到 HOC 再到 Hook
  4. 美团技术团队-Redux 从设计到源码
  5. 解析 snabbdom 源码,教你实现精简的 Virtual DOM 库
6b756747f9c64ae693f86d9cbd0fadae
5702ec12f83c46cdb832ef69202054c3
e54d849b41344b69b6d32950e3fb8a37
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值