而在现有 setState 逻辑实现中, 每调用一次 setState 就会执行 render 一次。因此在如下代码中, 每次点击增加按钮, 因为 click 方法里调用了 10 次 setState 函数, 页面也会被渲染 10 次。而我们希望的是每点击一次增加按钮只执行 render 函数一次。
export default class B extends Component {constructor(props) {super(props)this.state = {count: 0}this.click = this.click.bind(this)}click() {for (let i = 0; i < 10; i++) {this.setState({ // 在先前的逻辑中, 没调用一次 setState 就会 render 一次count: ++this.state.count})}}render() {console.log(this.state.count)return (<div><button onClick={this.click}>增加</button><div>{this.state.count}</div></div>)}}
查阅 setState 的 api, 其形式如下:
setState(updater, [callback])
它能接收两个参数, 其中第一个参数 updater 可以为对象或者为函数 ((prevState, props) => stateChange
), 第二个参数为回调函数;
确定优化思路为: 将多次 setState 后跟着的值进行浅合并, 并借助事件循环等所有值合并好之后再进行渲染界面。
let componentArr = []// 异步渲染function asyncRender(updater, component, cb) {if (componentArr.length === 0) {defer(() => render()) // 利用事件循环, 延迟渲染函数的调用}if (cb) defer(cb) // 调用回调函数if (_.isFunction(updater)) { // 处理 setState 后跟函数的情况updater = updater(component.state, component.props)}// 浅合并逻辑component.state = Object.assign({}, component.state, updater)if (componentArr.includes(component)) {component.state = Object.assign({}, component.state, updater)} else {componentArr.push(component)}}function render() {let componentwhile (component = componentArr.shift()) {renderComponent(component) // rerender}}// 事件循环, 关于 promise 的事件循环和 setTimeout 的事件循环后续会单独写篇文章。const defer = function(fn) {return Promise.resolve().then(() => fn())}
此时, 每点击一次增加按钮 render 函数只执行一次了。