完全是不可能滴, 这辈子都不可能完全! -- 来自某非著名码农
本文总结 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 <h2>App组件</h2>; } }
-
总结
-
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 ( <div> <h2>App组件</h2> <p>这是App组件的内容</p> </div> ); } }
- 效果
-
-
after
-
代码
export default class App extends Component { render() { return ( <Fragment> <h2>App组件</h2> <p>这是App组件的内容</p> </Fragment> ); } }
- 效果
-
- 总结:使用 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 <WrappedComponent />; } } } // 使用 withHoc(App)
-
向其中传参
function withHoc(params) { return (WrappedComponent) => { return class extends Component { render () { return <WrappedComponent />; } } } } // 使用 withHoc('hello hoc')(App)
-
接受props
function withHoc(params) { return (WrappedComponent) => { return class extends Component { render () { // 将接受的 props 传递给包装组件使用 return <WrappedComponent {...this.props}/>; } } } }
-
定义组件名称
function withHoc(params) { return (WrappedComponent) => { return class extends Component { // 定义静态方法,修改组件在调试工具中显示的名称 static displayName = `Form(${getDisplayName(WrappedComponent)})` render () { return <WrappedComponent {...this.props}/>; } } } } // 封装获取包装组件的 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 ( <div> <h2>App组件</h2> <LoadableComponent /> </div> ); } }
-
Suspense
和lazy
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 ( <div> <h2>App组件</h2> <Suspense fallback={<Loading />}> <LazyComponent /> </Suspense> </div> ); } }
-
区别
-
react-loadable
是民间 --> 需要额外下载引入 -
Suspense
和lazy
是官方 --> 只需引入 -
react-loadable
支持服务器渲染 -
Suspense
和lazy
不支持服务器渲染
-
- 总结:使用 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 ( <div>App组件</div> ); } } /* =============== 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( <Provider store={store}> <App /> </Provider>, document.getElementById('root'));
-
-
总结:
- 我们会发现使用
Redux
会变得更加复杂,以及多了很多模板代码(例如: action creators) - 但是,这是
Redux
能帮助我们更好操作状态,追踪和调试错误等。 - 并且
Redux
有着一整套丰富的生态圈,这些你都能在 官方文档 找到答案 - 总之,目前比起世面上
mobx
等库,更适用于大型项目开发~
- 我们会发现使用
10、未来可期
其实还有很多技术没有说,像 context
和 React Hooks
等,但受限于笔者的眼界,目前没有发现大规模使用的场景(如果有,请小伙伴们指正),所以就不谈了~有兴趣的小伙伴去找找看吧~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。