资讯专栏INFORMATION COLUMN

你还没有听过React不完全手册?

siberiawolf / 3092人阅读

摘要:因为节点跨层级的移动操作少到可以忽略不计如果父节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。注意官方建议不要进行节点跨层级的操作,非常影响性能。对处于同一层级的节点进行对比。支持异常边界处理异常。

完全是不可能滴, 这辈子都不可能完全!          --  来自某非著名码农

本文总结 React 实用的特性,部分实验性和不实用的功能将不会纳入进来,或许未来可期~

1、setState

面试题

class App extends Component {
  state = {
    val: 0
  }
  // 震惊!隔壁老王也有失手的时候~
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val); // ?
    
    this.setState((prevState) => ({val: prevState.val + 1}));
    console.log(this.state.val); // ?
      
    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val); // ?
      
      this.setState({val: this.state.val + 1});
      console.log(this.state.val); // ?
    }, 1000)
  }
  
  render() {
    return 

App组件

; } }

总结

setState 只在 React 合成事件和钩子函数中是“异步”的,在原生DOM事件和定时器中都是同步的。

如果需要获取“异步”场景的 setState 的值 --> this.setState(partial, callback) 在 callback 中拿到最新的值

如果要在“异步”场景保证同步更新多次 setState --> this.setState((prevState, props) => {return newState})

能保证同步更新, 但是外面获取的值还是之前的值

2、Fragment

before

代码

export default class App extends Component {
  render() {
    return (
      

App组件

这是App组件的内容

); } }

效果

after

代码

export default class App extends Component {
  render() {
    return (
      
        

App组件

这是App组件的内容

); } }

效果

总结:使用 Fragment ,可以不用添加额外的DOM节点

3、React性能优化

shouldComponentUpdate

// 举个栗子:
shouldComponentUpdate(nextProps, nextState) {
  if (nextProps !== this.props) {
    return true;  // 允许更新
  }
  if (nextState !== this.state) {
    return true;
  }
  return false;  // 不允许更新
}

PureComponent 组件

使用

// 实现了对 state 和 props 的浅比较
// 相等就不更新,不相等才更新
class App extends PureComponent {}

浅比较源码

// 实现 Object.is() 方法, 判断x y是否完全相等
function is(x, y) {
  // (x !== 0 || 1 / x === 1 / y) 用于判断 0 和 -0 不相等
  // x !== x && y !== y     用于判断 NaN 等于 NaN
  return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y
  ;
}

// 提取了hasOwnProperty方法,缓存
var hasOwnProperty$1 = Object.prototype.hasOwnProperty;

// 返回false为更新,true为不更新
function shallowEqual(objA, objB) {
  // 如果A和B完全相等,返回true
  if (is(objA, objB)) {
    return true;
  }
  // 如果A和B不相等,并且不是对象,说明就是普通值,返回false
  if (typeof objA !== "object" || objA === null || typeof objB !== "object" || objB === null) {
    return false;
  }
  // 提取A和B的所有属性
  var keysA = Object.keys(objA);
  var keysB = Object.keys(objB);
  // 如果长度不相等,返回false
  if (keysA.length !== keysB.length) {
    return false;
  }
  
  // 检测 A 的属性 和 B 的属性是否一样
  for (var i = 0; i < keysA.length; i++) {
    if (!hasOwnProperty$1.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false;
    }
  }
  
  return true;
}

问题:如果使用 pureComponent 只能进行浅比较,如果修改了原数据再更新,就会导致地址值一样从而不会更新。但实际需要更新。

解决:

手动保证每次都是新的值

使用 immutable-js 库,这个库保证生成的值都是唯一的

var map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
// 设置值
var map2 = map1.set("a", 66);
// 读取值
map1.get("a"); // 1
map2.get("a"); // 66

总结:使用以上方式,可以减少不必要的重复渲染。

4、React 高阶组件

基本使用

// WrappedComponent 就是传入的包装组件
function withHoc(WrappedComponent) {
  return class extends Component {
    render () {
      return ;
    }
  }
}

// 使用
withHoc(App)

向其中传参

function withHoc(params) {
  return (WrappedComponent) => {
    return class extends Component {
      render () {
        return ;
      }
    }
  }
}

// 使用
withHoc("hello hoc")(App)

接受props

function withHoc(params) {
  return (WrappedComponent) => {
    return class extends Component {
      render () {
        // 将接受的 props 传递给包装组件使用
        return ;
      }
    }
  }
}

定义组件名称

function withHoc(params) {
  return (WrappedComponent) => {
    return class extends Component {
      // 定义静态方法,修改组件在调试工具中显示的名称
      static displayName = `Form(${getDisplayName(WrappedComponent)})`
      render () {
        return ;
      }
    }
  }
}
// 封装获取包装组件的 displayName 的方法
function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || "Component";
}

原文链接
5、render props
原文太长,直接上 链接 

官网真香, 建议大家将 React 官网过一遍~
6、React 懒加载

react-loadable

import Loadable from "react-loadable";
import Loading from "./components/loading"

const LoadableComponent = Loadable({
  loader: () => import("./components/home"),
  loading: Loading,
});

export default class App extends Component {
  render() {
    return (
      

App组件

); } }

Suspenselazy

import React, {Component, Suspense, lazy} from "react";
import Loading from "./components/loading";

const LazyComponent = lazy(() => import("./components/home"));

export default class App extends Component {
  render() {
    return (
      

App组件

}>
); } }

区别

react-loadable 是民间 --> 需要额外下载引入

Suspenselazy 是官方 --> 只需引入

react-loadable 支持服务器渲染

Suspenselazy 不支持服务器渲染

总结:使用 create-react-app 会将其多带带提取成一个bundle输出,从而资源可以懒加载和重复利用。

7、虚拟DOM diff算法

虚拟DOM diff算法主要就是对以下三种场景进行优化:

tree diff

对树进行分层比较,两棵树只会对同一层次的节点进行比较。(因为 DOM 节点跨层级的移动操作少到可以忽略不计)

如果父节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。

注意:

React 官方建议不要进行 DOM 节点跨层级的操作,非常影响 React 性能。

在开发组件时,保持稳定的 DOM 结构会有助于性能的提升。例如,可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。

component diff

如果是同一类型的组件,按照原策略继续比较 virtual DOM tree(tree diff)。

对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。

如果不是,直接替换整个组件下的所有子节点。

element diff

对处于同一层级的节点进行对比。

这时 React 建议:添加唯一 key 进行区分。虽然只是小小的改动,性能上却发生了翻天覆地的变化!

如: A B C D --> B A D C

添加 key 之前: 发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。

添加 key 之后: B、D 不做任何操作,A、C 进行移动操作,即可。

建议:在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

总结

React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;

React 通过分层求异的策略,对 tree diff 进行算法优化;

React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;

React 通过设置唯一 key的策略,对 element diff 进行算法优化;

建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;

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

原文链接
8、Fiber

Fiber 是为了解决 React 项目的性能问题和之前的一些痛点而诞生的。

Fiber 的核心流程可以分为两个部分:

可中断的 render/reconciliation 通过构造 workInProgress tree 得出 change。

不可中断的 commit 应用这些 DOM change。

异步实现不同优先级任务的协调执行:

requestIdleCallback: 在线程空闲时期调度执行低优先级函数;

requestAnimationFrame: 在下一个动画帧调度执行高优先级函数;

总结

可切分,可中断任务。

可重用各分阶段任务,且可以设置优先级。

可以在父子组件任务间前进/后退切换任务。

render方法可以返回多元素(即可以返回数组)。

支持异常边界处理异常。

原文链接: 

https://mp.weixin.qq.com/s/uD...
https://juejin.im/post/5a2276...
9、Redux

作用: 集中管理多个组件共享的状态

特点: 单一数据源、纯函数、只读state

redux 核心模块定义:

store.js

import { createStore, applyMiddleware } from "redux";
// 异步actions使用的中间件
import thunk from "redux-thunk";
// redux开发chrome调试插件
import { composeWithDevTools } from "redux-devtools-extension";

import reducers from "./reducers";

export default createStore(reducers, composeWithDevTools(applyMiddleware(thunk)));

reducers.js

import { combineReducers } from "redux";
import { TEST1, TEST2 } from "./action-types";

function a(prevState = 0, action) {
  switch (action.type) {
    case TEST1 :
      return action.data + 1;
    default :
      return prevState;
  }
}

function b(prevState = 0, action) {
  switch (action.type) {
    case TEST2 :
      return action.data + 1;
    default :
      return prevState;
  }
}
// 组合两个reducer函数并暴露出去
export default combineReducers({a, b});   

actions.js

import { TEST1, TEST2 } from "./action-types";
// 同步action creator,返回值为action对象
export const test1 = (data) => ({type: TEST1, data});
export const test2 = (data) => ({type: TEST2, data});
// 异步action creator,返回值为函数
export const test2Async = (data) => {
  return (dispatch) => {
    setTimeout(() => {
      dispatch(test2(data));
    }, 1000)
  }
};

action-types.js

export const TEST1 = "test1";
export const TEST2 = "test2";

组件内使用:

App.jsx

import React, { Component } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import {test1, test2Async} from "./redux/actions";

class App extends Component {
  static propTypes = {
    a: PropTypes.number.isRequired,
    b: PropTypes.number.isRequired,
    test1: PropTypes.func.isRequired,
    test2Async: PropTypes.func.isRequired,
  }
  
  componentDidMount() {
    const { a, b, test1, test2Async } = this.props;
    // 测试
    test1(a + 1);
    test2Async(b + 1);
  }
  
  render() {
    return (
      
App组件
); } } /* =============== redux相关代码 ================== */ // 将状态数据映射为属性以props方式传入组件 const mapStateToProps = (state) => ({a: state.a, b: state.b}); // 将操作状态数据的方法映射为属性以props方式传入组件 const mapDispatchToProps = (dispatch) => { return { test1(data) { dispatch(test1(data)); }, test2Async(data) { dispatch(test2Async(data)); } } } // connect就是一个典型的HOC export default connect(mapStateToProps, mapDispatchToProps)(App); /* // 上面写的太复杂了,但是好理解。而以下就是上面的简写方式 export default connect( (state) => ({...state}), { test1, test2Async } )(App); */

index.js

// 入口文件的配置
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import App from "./App";
import store from "./redux/store";

ReactDOM.render(
  
    
  , document.getElementById("root"));

总结:

我们会发现使用 Redux 会变得更加复杂,以及多了很多模板代码(例如: action creators)

但是,这是 Redux 能帮助我们更好操作状态,追踪和调试错误等。

并且 Redux 有着一整套丰富的生态圈,这些你都能在 官方文档 找到答案

总之,目前比起世面上 mobx 等库,更适用于大型项目开发~

10、未来可期

其实还有很多技术没有说,像 contextReact Hooks 等,但受限于笔者的眼界,目前没有发现大规模使用的场景(如果有,请小伙伴们指正),所以就不谈了~有兴趣的小伙伴去找找看吧~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109272.html

相关文章

  • 前端性能优化完全手册

    摘要:负载均衡就是用来帮助我们将众多的客户端请求合理的分配到各个服务器,以达到服务端资源的充分利用和更少的请求时间。如下面的配置复制代码这样可以完美绕过浏览器的同源策略访问的属于同源访问,而对服务端转发的请求不会触发浏览器的同源策略。 性能优化是一门大学问,本文仅对个人一些积累知识的阐述,欢迎下面补充。 抛出一个问题,从输入url地址栏到所有内容显示到界面上做了哪些事? 1.浏览器向 DN...

    ranwu 评论0 收藏0
  • 前端性能优化完全手册

    摘要:负载均衡就是用来帮助我们将众多的客户端请求合理的分配到各个服务器,以达到服务端资源的充分利用和更少的请求时间。如下面的配置复制代码这样可以完美绕过浏览器的同源策略访问的属于同源访问,而对服务端转发的请求不会触发浏览器的同源策略。 性能优化是一门大学问,本文仅对个人一些积累知识的阐述,欢迎下面补充。 抛出一个问题,从输入url地址栏到所有内容显示到界面上做了哪些事? 1.浏览器向 DN...

    jayce 评论0 收藏0
  • 翻译 | React AJAX最佳实践

    摘要:作者沪江前端开发工程师本文原创翻译,有不当的地方欢迎指出。管理数据,而提供服务器上的数据,因此应用于处理网络请求。结论使用建立的应用都是模块化的会成为其中一个模块,库是另一个模块。原文原创新书移动前端高效开发实战已在亚马逊京东当当开售。 作者:Oral (沪江Web前端开发工程师)本文原创翻译,有不当的地方欢迎指出。转载请指明出处。 当你问起有关AJAX与React时,老司机们首先就会...

    DirtyMind 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<