- 组件的数据来源有两个地方,分别是属性对象和状态对象
- 属性是父组件传递过来的,不可更改
- 状态是自己内部的,改变状态的唯一方式就是setState
- 属性和状态的变化都会引起视图更新
import React from "react"; import ReactDOM from "react-dom"; /** * 属性是由父组件传递过来的,不能改变 * 状态是组件内部,由自己维护,外界无法访问 改变状态的唯一方式就是setState */ class Counter extends React.Component{ constructor(props) { //构造函数是唯一给状态赋值的地方
super(props) //定义状态的地方
this.state = {number: 0} } render(){ //当我们调用setState的时候会引起状态的改变和组件的更新
console.log('render') return ( <div>
<p>{this.state.number}</p>
<button onClick={() => this.setState({number: this.state.number + 1})}>+</button>
</div>
) } } // let counter = new Counter()
ReactDOM.render(<Counter></Counter>,document.getElementById('root'))
构造函数是唯一定义状态并且赋值的地方,当我们要改变状态的值的时候需要通过setState方法,而不是直接修改state的值,并且每次调用setState的时候会引起状态的改变和组件的更新。
1.不能直接修改state的值
import React from "react"; import ReactDOM from "react-dom"; /** * 属性是由父组件传递过来的,不能改变 * 状态是组件内部,由自己维护,外界无法访问 改变状态的唯一方式就是setState */ class Counter extends React.Component{ constructor(props) { //构造函数是唯一给状态赋值的地方
super(props) //定义状态的地方
this.state = {number: 0} } add = () => { this.state.number += 1 } render(){ //当我们调用setState的时候会引起状态的改变和组件的更新
console.log('render') return ( <div>
<p>{this.state.number}</p>
<button onClick={this.add}>+</button>
</div>
) } } // let counter = new Counter()
ReactDOM.render(<Counter></Counter>,document.getElementById('root'))
像这种直接修改state值得方法并不会生效。
2.state的更新可能是异步
我们知道调用setState会触发更新操作,这个过程包括更新state,创建新的VNode,在经过diff算法对比差异,决定需要渲染那一部分,假如他是同步更新的话,每次调用都要执行一次前面的流程,这样会造成很大的性能问题,所以需要将多个setState放进一个队列里面,、然后再一个一个执行,最后再一次性更新视图,这样会提高性能。举个例子:
let state = {number: 0} function setState(newState) { state = newState console.log(state) } setState({number: state.number + 1}) setState({number: state.number + 2}) setState({number: state.number + 3})
这段代码会通过 setState 方法改变state值,我们看看打印结果:
可以看到每次调用setState都会改变state的值并且进行渲染,这将是一个非常消耗性能的问题。
所以React针对setState做了一些特别的优化:将多个setState的调用放进了一个队列,合并成一个来执行,这意味着当调用setState时,state并不会立即更新,看下面这个例子:
let state = {number: 0} let updataQueue = [] function setState(newState) { updataQueue.push(newState) } setState({number: state.number + 1}) setState({number: state.number + 2}) setState({number: state.number + 3}) updataQueue.forEach(item =>{ state = item }) console.log(state) // 3
我们预想的是结果等于6.但是输出的却是3,这是因为变成异步更新之后state的值并不会立即更新,所以每次拿到的state都是 0 ,如果我们想要让结果等于 6,也就是每次都能拿到最新值,那就需要给setState()传递一个函数作为参数,在这个函数中可以拿到每次改变后的值,并通过这个函数的返回值得到下一个状态。
let state = { number: 0 }
let updataQueue = [] //更新函数队列
let callbackQueue = [] //回调函数队列
function setState(updataState,callback) { //入队
updataQueue.push(updataState) callbackQueue.push(callback) } //清空队列
function flushUpdata () { for(let i = 0; i < updataQueue.length; i++) { state = updataQueue[i](state) //拿到每次改变后的值作为下一个的状态 } state = state callbackQueue.forEach(callbackItem => callbackItem()) } function add(){ setState(preState => ({ number: preState.number + 1}),() => { console.log(state) }) setState(preState => ({ number: preState.number + 2}),() => { console.log(state) }) setState(preState => ({ number: preState.number + 3}),() => { console.log(state) }) //批量更新
flushUpdata() } add() console.log(state) // 6
由于回调函数也是异步执行的,所以最后一次性输出的都是6.
改写成class类的形式如下:
class Component { constructor() { this.state = { number: 0 } this.batchUpdata = false
this.updataQueue = [] //更新队列
this.callbackQueue = [] //回调函数队列
} setState(updataState, callback) { if (this.batchUpdata) { this.updataQueue.push(updataState) //放入队列
this.callbackQueue.push(callback) } } flushUpdata() { let state = this.state // this.updataQueue.forEach(newStateitem => this.state = newStateitem)
for(let i = 0; i < this.updataQueue.length; i++) { state = this.updataQueue[i](state) } this.state = state this.callbackQueue.forEach(callback => callback()) } add() { this.batchUpdata = true //开启合并模式
this.setState(preState => ({ number: preState.number + 1}),() => { console.log(this.state) }) this.setState(preState => ({ number: preState.number + 2}),() => { console.log(this.state) }) this.setState(preState => ({ number: preState.number + 3}),() => { console.log(this.state) }) //批量更新
this.flushUpdata() } } let c = new Component() c.add() console.log(c.state)
现在这个逻辑对于setState传入的参数是函数很适合,但是有时候我们希望传入的是对象,且希望利用setState执行完之后做一些操作,比如在请求到数据之后隐藏进度条等,这个时候就需要setState能变为同步执行,这个时候我们会借助promise、setTimeout等方法来改变setState让它变为同步的。也就是不用放入队列,而是立即执行,但是以上逻辑不支持同步的情况,我们需要修改:
class Component { constructor() { this.state = { number: 0 } this.batchUpdata = false
this.updataQueue = [] //更新队列
this.callbackQueue = [] //回调函数队列
} setState(updataState, callback) { if (this.batchUpdata) { //批量更新
this.updataQueue.push(updataState) //放入队列
this.callbackQueue.push(callback) }else { //直接更新
console.log('直接更新') //如果是函数需要把老值传进去
if(typeof updataState === 'function') { this.state = updataState(this.state) }else { this.state = updataState } } } flushUpdata() { let state = this.state // console.log(this.updataQueue)
for(let i = 0; i < this.updataQueue.length; i++) { //为了兼容参数为函数和对象的情况需要判断一下 参数为对象的时候不用传上一个的状态值,参数为函数的时候需要传上一个的状态给下一个状态
if(typeof this.updataQueue[i] === 'function') { state = this.updataQueue[i](state) }else { state = this.updataQueue[i] } } this.state = state this.callbackQueue.forEach(callback => { if(callback) callback() //为了兼容参数为函数和对象的情况需要判断一下,参数为对象的时候没有回调函数就不执行
}) this.batchUpdata = false //更新完毕置为false
} add() { this.batchUpdata = true //开启合并模式
//不会放进更新队列
setTimeout(() => { this.setState({number: this.state.number + 4}) console.log(this.state) },1000) this.setState({number: this.state.number + 1}) // this.setState(preState => ({ number: preState.number + 1}),() => {
// console.log(this.state)
// })
// this.setState({number: this.state.number + 1})
//批量更新
this.flushUpdata() } } let c = new Component() c.add() console.log('end'+ JSON.stringify(c.state))
批量处理机制就是为了减少setState刷新页面的次数,setTimeout,promise等异步方法可以直接跳过批量处理机制,setState调几次就改几次。
https://www.cnblogs.com/jiuyi/p/9263114.html这篇文章对于同步更新讲的比较好
3.seState的更新会被合并
当调用setState的时候,React会把你要修改的那一部分的对象合并到当前的state上面,举个栗子:
class Counter extends React.Component{ constructor(props) { //构造函数是唯一给状态赋值的地方
super(props) this.add = this.add.bind(this) //定义状态的地方
this.state = {name: 'leah' ,number: 0} } add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
this.setState({number: this.state.number + 1}) } render(){ console.log(this) //当我们调用setState的时候会引起状态的改变和组件的更新
console.log('render') return ( <div>
<p>{this.state.name}</p>
<p>{this.state.number}</p>
<button onClick={this.add}>+</button>
</div>
) } }
当前我们只修改了state.number这个时候,name还是会渲染,我们需要对这部分进行合并
class Component { constructor() { this.state = { name: 'leah', number: 0 } this.batchUpdata = false
this.updataQueue = [] //更新队列
this.callbackQueue = [] //回调函数队列
} setState(updataState, callback) { if (this.batchUpdata) { //批量更新
this.updataQueue.push(updataState) //放入队列
this.callbackQueue.push(callback) }else { //直接更新
console.log('直接更新') //如果是函数需要把老值传进去
if(typeof updataState === 'function') { this.state = updataState(this.state) }else { this.state = updataState } } } flushUpdata() { let state = this.state // console.log(this.updataQueue)
for(let i = 0; i < this.updataQueue.length; i++) { //为了兼容参数为函数和对象的情况需要判断一下 参数为对象的时候不用传上一个的状态值,参数为函数的时候需要传上一个的状态给下一个状态
let partialState = typeof this.updataQueue[i] === 'function' ? this.updataQueue[i](this.state) : this.updataQueue[i] state = {...state, ...partialState} } this.state = state this.callbackQueue.forEach(callback => { if(callback) callback() //为了兼容参数为函数和对象的情况需要判断一下,参数为对象的时候没有回调函数就不执行
}) this.batchUpdata = false //更新完毕置为false
} add() { this.batchUpdata = true //开启合并模式
//不会放进更新队列
setTimeout(() => { this.setState({number: this.state.number + 4}) console.log(this.state) },1000) this.setState({number: this.state.number + 1}) // this.setState(preState => ({ number: preState.number + 1}),() => {
// console.log(this.state)
// })
// this.setState({number: this.state.number + 1})
//批量更新
this.flushUpdata() } } let c = new Component() c.add() console.log('end'+ JSON.stringify(c.state))
4.在组件实例中this的指向问题:
一般来说,类的方法里this是undefined,那如何让普通方法的this指向组件实例呢?
4.1.箭头函数
class Counter extends React.Component{ constructor(props) { //构造函数是唯一给状态赋值的地方
super(props) //定义状态的地方
this.state = {number: 0} } add = (event) => { console.log(event) // this.state.number += 1 不能直接修改state的值
this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //当我们调用setState的时候会引起状态的改变和组件的更新
console.log('render') return ( <div>
<p>{this.state.number}</p>
<button onClick={this.add}>+</button>
</div>
) } }
4.2.匿名函数
class Counter extends React.Component{ constructor(props) { //构造函数是唯一给状态赋值的地方
super(props) //定义状态的地方
this.state = {number: 0} } add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //当我们调用setState的时候会引起状态的改变和组件的更新
console.log('render') return ( <div>
<p>{this.state.number}</p>
<button onClick={() => this.add()}>+</button>
</div>
) } }
4.3.bind绑定
class Counter extends React.Component{ constructor(props) { //构造函数是唯一给状态赋值的地方
super(props) //定义状态的地方
this.state = {number: 0} } /** * 合成事件 react合成事件 * 事件代理 * event 并不是原始的dom对象 而是react二次封装的事件对象 可以复用 */ add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //当我们调用setState的时候会引起状态的改变和组件的更新
console.log('render') return ( <div>
<p>{this.state.number}</p>
<button onClick={this.add.bind(this)}>+</button>
</div>
) } }
但是这样绑定有个问题,就是每次渲染的时候都需要绑定一次,所以可以在构造函数里面一次性绑定
class Counter extends React.Component{ constructor(props) { //构造函数是唯一给状态赋值的地方
super(props) this.add = this.add.bind(this) //定义状态的地方
this.state = {number: 0} } /** * 合成事件 react合成事件 * 事件代理 * event 并不是原始的dom对象 而是react二次封装的事件对象 可以复用 */ add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //当我们调用setState的时候会引起状态的改变和组件的更新
console.log('render') return ( <div>
<p>{this.state.number}</p>
<button onClick={this.add}>+</button>
</div>
) } }
5.合成事件
合成事件原理:利用事件冒泡机制
如果react事件绑定在了真实DOM节点上,一个节点同事有多个事件时,页面的响应和内存的占用会受到很大的影响。因此SyntheticEvent作为中间层出现了。
事件没有在目标对象上绑定,而是在document上监听所支持的所有事件,当事件发生并冒泡至document时,react将事件内容封装并叫由真正的处理函数运行。
这篇主要讲了一下setState的异步更新处理过程。
我们的更新其实并不是真正的异步处理,而是更新的时候把更新内容放到了更新队列中,最后批次更新,这样才表现出异步更新的状态。setTimeout,promise等异步方法可以直接跳过批量处理机制,setState调几次就改几次。