setState是react开发中很重要的一个方法,在react的官方文档中介绍了setState正确使用的三件事:
- 不要直接修改 State
- State 的更新可能是异步的
- State 的更新会被合并
State 的更新会被合并
官方文档中讲到,出于性能的考虑,react通常会把多个setState()
合并成一个调用,从而提高渲染的性能,因此下面的代码,实际上只更新了一次:
this.state = {index:0};
componentDidMount(){
this.setState({ index: this.state.index + 1 }) // {index:0}
this.setState({ index: this.state.index + 1 }) // {index:0}
}
如果要解决这个问题,可以在setState中传入回调函数:
this.state = {index:0};
componentDidMount(){
this.setState((state)=>({index:state.index+1}));
this.setState((state)=>({index:state.index+1}));
}
State 是否异步更新
this.state = {index:0};
componentDidMount(){
this.setState({ index: this.state.index + 1 }) //{index:0}
this.setState({ index: this.state.index + 1 }) // {index:0}
setTimeout(() => {
this.setState({ index: this.state.index + 1 }) //{index:2}
this.setState({ index: this.state.index + 1 }) //{index:3}
})
}
可以看到,在DidMount函数中,setState
的执行结果在作用域和异步函数的区别。
需求
模拟一个计数器按钮,每点击一次按钮,数字累加。
一、初始化
编辑index.html
,编写渲染所需的element:
// index.html
<div id="root"></div>
二、渲染
编辑counter.js
,完成下面函数编写:
- render (渲染所需dom)
- getElement (获取真实的dom,添加事件和方法)
- mounted (初始化完成回调,接收绑定元素id作为参数)
class Counter {
constructor(){
this.domEl = null;
this.state = {index:0}
}
getElement(){
const dom = document.createElement("div");
dom.innerHTML = this.render();
return dom.children[0]
}
render(){
return `<button>${this.state.index}</button>`
}
mounted(id){
this.domEl = this.getElement();
document.getElementById(id).appendChild(this.domEl)
}
}
渲染到 root
:
new Counter().mounted("root");
三、更新
Component 负责新增setState
和update
更新和重新渲染节点:
class Component {
...
setState(state){ // 新增setState方法,更新state数据
Object.assign(this.state,state);
this.update();
}
update(){ // 新增更新方法,渲染改变后的dom元素
let oldDom = this.domEl;
let newDom = this.getElement();
this.domEl = newDom;
oldDom.parentNode.replaceChild(newDom, oldDom)
}
...
}
继承 Component,新增 add
方法触发更新:
class Counter extends Component{
constructor(){
super();
this.state = {index:0}
}
add(){
this.setState({index:this.state.index+1});
}
render(){
return `<button>${this.state.index}</button>`
}
}
四、事件
给按钮绑定事件:
render(){
return `<button onclick="trigger(event,'add')">${this.state.index}</button>`
}
function trigger(event,method,...params) {
const component = event.target.component
component[method].apply(component, params);
}
为了让 trigger
获得元素 target,获取元素前做绑定操作:
getElement() {
const dom = document.createElement('div')
dom.innerHTML = this.render();
+ const el = dom.children[0];
+ el.component = this; // 把自己绑定到component属性中
+ return el;
}
事件添加完成,点击按钮可以实现数字累加。
五、批量任务管理
任务管理器:
const batchingStrategy = {
isBatchingUpdates: false, // 是否批量更新
updaters: [], //存储更新函数的数组
batchedUpdates(){} // 执行更新方法
}
batchingStrategy
对象用来管理批量更新的任务和状态。
缓存批量任务更新器:
class Updater {
constructor(component) {
this.component = component
this.pendingStates = [] // 暂存需要更新的state
}
addState(particalState) {
this.pendingStates.push(particalState);
if (batchingStrategy.isBatchingUpdates) {
batchingStrategy.updaters.push(this)
} else {
this.component.updateComponent()
}
}
}
实例化Update,新增updateComponent
和 addState
:
class Component {
constructor(props) {
this.props = props;
this.domEl = null;
this.$updater = new Updater(this);
}
setState(state) {
this.$updater.addState(state)
}
updateComponent(){
while (this.$updater.pendingStates.length) {
this.state = Object.assign(
this.state,
this.$updater.pendingStates.shift()
)
}
this.update()
}
}
修改事件触发 trigger
方法,逻辑如下:
isBatchingUpdates = true
// 更新start- 缓存更新方法
isBatchingUpdates = false
// 更新end- 执行缓存区更新方法
function trigger(event,method,...params) {
batchingStrategy.isBatchingUpdates = true;
const component = event.target.component
component[method].apply(component, params);
batchingStrategy.isBatchingUpdates = false
batchingStrategy.batchedUpdates()
}
...
//依次执行
batchedUpdates() {
while (this.updaters.length) {
this.updaters.shift().component.updateComponent()
}
}
更新的流程图
现在add
方法执行结果如下:
add(params){
this.setState({index:this.state.index+1}); //{index:0}
this.setState({index:this.state.index+1}); //{index:0}
setTimeout(() => {
this.setState({index:this.state.index+1}); //{index:2}
this.setState({index:this.state.index+1}); //{index:3}
}, 0);
}
六、事务
事务(transaction
)
目的:优化代码结构
原理:initCallbacks => customCallbacks => endCallbacks[]
- 依次执行初始化函数数组
type initialize = Function[]
- 执行用户自定义函数
customCallbacks()
- 依次执行结束函数数组
type close = Function[]
class Transaction {
constructor(wrappers) {
this.wrappers = wrappers
}
perform(func) {
this.wrappers.forEach((wrapper) => wrapper.initialize())
func.call()
this.wrappers.forEach((wrapper) => wrapper.close())
}
};
// 实例化事务
const transact = new Transaction([
{
initialize() {
batchingStrategy.isBatchingUpdates = true
},
close() {
batchingStrategy.isBatchingUpdates = false
batchingStrategy.batchedUpdates()
},
},
]);
// 执行
function trigger(event,method,...params) {
const component = event.target.component;
transact.perform(component[method].bind(component, params))
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。