目录
1. setState()的说明
使用setState的原因
在开发中我们并不能直接通过修改state的值来让界面发生更新:
因为我们修改了state之后,希望React根据最新的State来重新渲染界面,但是this.state这种方式的修改React并不知道数据发生了变化。
在组件中并没有实现setState的方法,为什么可以调用呢?
原因很简单,setState方法是从Component中继承过来的。 我有找到对应的源码, 可以看到源码中, setState是放在Component的原型上的
setState推荐语法
- 推荐:使用
setState((state, props) => {})
语法- 参数state:表示最新的state
- 参数props:表示最新的props
//推荐语法
// 注意:这种语法是异步更新state的!
handleClick = ()=>{
this.setState((state,props)=>{
return{
conut:state.conut +1
}
})
console.log('count:', this.state.conut)///1
}
第二个参数
- 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
- 语法:
setState(updater[,callback])
handleClick = ()=>{
this.setState(
(state,props)=>{},
()=>{console.log('这个回调函数会在状态更新后立即执行')}
)
}
handleClick = ()=>{
this.setState(
(state,props)=>{},
()=>{document.title='更新state后的标题:'+this.state.count}
)
}
setState的基本用法
setState常见的用法
用法一:
直接在setState函数中传入一个对象, 传入的该对象会和this.state的对象进行一个合并, 相同的属性会进行覆盖,
修改完成后等待任务队列批处理调用render函数实现页面更新
export class App extends Component {
constructor() {
super()
this.state = {
name:"小莉"
}
}
changeText() {
this.setState({
name:"猪"
})
}
render() {
const { name } = this.state
return (
<div>
<h2>{name}</h2>
<button onClick={() => {this.changeText()}}>点我</button>
</div>
)
}
}
用法二:
在setState函数中传入一个回调函数, 要求传入的回调函数返回一个对象,React内部会将返回的这个对象和state对象进行合并
用法二的好处:
- 可以在该回调函数中, 编写修改state数据的逻辑, 具有更强的内聚性
- 当前回调函数会默认将state和props传入进来, 我们可以在回调函数中直接获取, 不需要再通过this.state或this.props的方式获取
export class App extends Component {
constructor() {
super()
this.state = {
name:"小莉"
}
}
changeText() {
this.setState((state, props) => {
// 可以直接在回调函数中获取state和props
console.log(state, props);
// 对数据进行操作的逻辑
const name = state.name + "小莉"
// 该回调函数返回一个对象
return {
name
}
})
}
render() {
const { name } = this.state
return (
<div>
<h2>{name}</h2>
<button onClick={() => {this.changeText()}}>点我</button>
</div>
)
}
}
setState是异步更新的
可以用以下代码检验,在执行完setState后, 我们立即打印一下name的结果
export class App extends Component {
constructor() {
super()
this.state = {
name:"小莉"
}
}
changeText() {
// 用法一
this.setState({
name:"猪"
})
console.log(this.state.name) // 小莉
}
render() {
const { name } = this.state
return (
<div>
<h2>{name}</h2>
<button onClick={() => {this.changeText()}}>点我</button>
</div>
)
}
}
打印结果为“小莉”, 并不是修改后的结果,
可见setState是异步的操作,我们并不能在执行完setState之后立马拿到最新的state的结果
那么为什么setState设计为异步呢?
设计为异步的两个主要原因
1. setState设计为异步,可以显著的提升性能:
我们知道调用setState会让render函数重新执行,如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的。最好的办法应该是获取到多个更新,之后进行批量更新(或者说统一的更新)。而React中的做法也是如此,在一个时间段中,会获取多个更新,再将多个更新放入一个任务队列中,再对任务队列进行批处理。如果还有其他更新不在当前时间段,则在下一个时间段(或者其他时间段)的任务队列中进行批处理
2. 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步:
state和props不能保持一致性,会在开发中产生很多的问题。
2. JSX语法的转化过程
- JSX仅仅是createElement()方法的语法糖(简化语法)
- JSX语法@babel/preset-react插件编译为createElent()方法
- React元素:是一个对象,用来描述你希望在屏幕上看到的内容
转化过程:
JSX语法 --> createElement() --> React元素
3. 组件更新机制
setState()
的两个作用:修改state和更新组件(UI)- 过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染
当前组件子树
(当前组件及其所有子组件)
4. 组件性能优化
减轻state
减轻state
:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading等)- 注意:不用做渲染的数据不要放在state中,比如定时器id等
- 对于这种需要在多个方法中用到的数据,应该放在this中
class Hello extends React.Component{
componentDidMount(){
// timerId存储到this中,而不是state中
this.timerId = setInterval(()=>{},200)
}
componentWillUnmount(){
clearInterval(this.timerId)
}
render(){...}
}
避免不必要的重新渲染
- 组件更新机制:父组件的更新会引起它的子组件也被更新,这种思路很清晰
- 问题:子组件没有任何变化时也会被更新
- 如何避免不必要的重新渲染呢?
解决方法:使用钩子函数shouldComponentUpdate(nextProps,nextState)
作用:通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
触发时机:更新阶段的钩子函数、组件重新渲染前执行( shouldComponentUpdate --> render )
class Hello extends React.Component{
shouldComponentUpdate(){
// 根据条件是否重新渲染组件
return false
}
render(){...}
}
随机数案例
class App extends React.Component{
// 因为两次生成的随机数可能相同,如果相同中,此时,不需要重新渲染
shouldComponentUpdate(nextProps,nextState){
//return nextState.number!==this.state.number 和以下代码等同
if(nextState.number===this.state.number){
return false
}
return true
}
render(){...}
}
class NumberBox extends React.Component{
shouldComponentUpdate(nextProps){
// 如果前后两次的number值相同,就返回false,不更新组件
//return nextProps.number!==this.props.number 和以下代码等同
if(nextProps.number===this.props.number){
return false
}
return true
}
render(){...}
}
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
class App extends React.Component{
state = {
number:0
}
handleClick = ()=> {
this.setState(()=>{
return {
number:Math.floor(Math.random() *3)
}
})
}
// 因为两次生成的随机数可能相同,如果相同中,此时,不需要重新渲染
shouldComponentUpdate(nextProps,nextState){
console.log('最新状态:',nextState,",当前状态:",this.state)
if(nextState.number===this.state.number){
return false
}
return true
}
render(){
return(
<div>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
class NumberBox extends React.Component{
shouldComponentUpdate(nextProps){
console.log('最新props:',nextProps,",当前props:",this.props)
// 如果前后两次的number值相同,就返回false,不更新组件
if(nextProps.number===this.props.number){
return false
}
return true
}
render(){
console.log('子组件中的render')
return <h1>随机数:{this.props.number}</h1>
}
}
ReactDOM.render(<App />,document.getElementById('root'))
纯组件
- 纯组件:
React.PureComponent
与React.Component 功能相似 - 区别:PureComponent内部自动实现了shouldComponentUpdate钩子函数,不需要手动比较
- 原理:纯组件内部通过分别
对比
前后的props和state的值,来决定是否重新渲染组件
class Hello extends React.PureComponent{
render(){
return <h1>纯组件</h1>
}
}
代码实现:
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.PureComponent{
state = {
number:0
}
handleClick = ()=> {
this.setState(()=>{
return {
number:Math.floor(Math.random() *3)
}
})
}
render(){
// console.log('父组件中的render')
return(
<div>
<NumberBox number={this.state.number}/>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
}
}
class NumberBox extends React.PureComponent{
render(){
console.log('子组件中的render')
return <h1>随机数:{this.props.number}</h1>
}
}
ReactDOM.render(<App />,document.getElementById('root'))
- 说明:纯组件内部的对比是
shallow compare
(浅层对比) - 对于值类型来说,比较这两个的值是否相同(直接赋值即可)
let number =0
let newNumber = number
newNumber = 2
console.log(number===newNumber) //false
state = {number:0}
setState({number:Math.floor(random() *3)})
}
// PureComponent内部对比
最新的state.number === 上一次的state.number //false,则重新渲染组件
- 对于
引用类型
来说:只比较对象的引用(地址)是否相同 - 注意:
state或props中属性值为引用类型时,应该创建新数据,不要直接修改原数据
const obj={number:0}
const newObj = obj
newObj.number = 2
console.log(newObj===obj) //true
// 正确!创建新数据
const newObj = {...state.obj,number:2}
setState({obj:newObj})
// 正确!创建新数据
// 不要用数组的push / unshift等直接修改当前数组的方法
// 而应该用concat 或 slice 等这些返回新数组的方法
this.setState({
list:[...this.state.list,{新数组}]
})
虚拟DOM和Diff算法
- React更新视图的思想是:只要state变化就重新渲染视图
- 特点:思路非常清晰
- 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?
不是
- 理想状态:
部分更新
,只更新变化的地方 - 问题:React是如何做到部分更新的?
虚拟DOM配合Diff算法
- 虚拟 DOM(Virtual DOM),就是一个 JS 对象,用来描述我们希望在页面中看到的 HTML 结构内容.
- 虚拟 DOM : 本质是一个 JS 对象,用来描述你希望在屏幕上看到的内容(UI)
虚拟DOM 对象 和 HTML 结构之间是一一对应的关系
执行过程
- 第一次页面渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)
过程:1 JSX + state => 2 虚拟 DOM 树(JS 对象) => 3 浏览器中看到的 HTML 结构内容 - 根据虚拟DOM生成真正的DOM,渲染到页面中
- 当数据变化时后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)
- 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容
- 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面