一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

七、setState详细解析和React性能优化

1、setState异步更新
import React from "react";

function Home(props) {
  return <h1>{props.message}</h1>
}

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: '黄婷婷'
    }
  }

  render() {
    return (<div>
      <h2>{this.state.message}</h2>
      <button onClick={e => this.changeMessage()}>按钮</button>
      <Home message={this.state.message}/>
    </div>)
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log('生命周期', this.state.message)
  }

  changeMessage() {
    /**
     * 1、setState是父类方法
     * 2、setState是异步更新
     *     * setState设计为异步,可以显著的提升性能
     *         - 如果每次调用setState都进行一次更新,那么意味着render函数会被频繁调用,
     *           界面重新渲染,这样效率是很低的
     *         - 最好的办法应该是获取到多个更新,之后进行批量更新
     *     * 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步
     *         - state和props不能保持一致性,会在开发中产生很多的问题
     * 3、获取异步更新后的数据
     *     - setState(更新的state, 回调函数)
     *     - componentDidUpdate生命周期函数(先于回调函数执行)
     */
    this.setState({
      message: '孟美岐'
    }, () => {
      console.log('回调函数', this.state.message)
    })
    console.log('异步更新', this.state.message)
  }
}
2、setState同步更新
import React from "react";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: '黄婷婷'
    }
  }

  render() {
    return (<div>
      <h2>{this.state.message}</h2>
      <button onClick={e => this.changeMessage()}>按钮1</button>
      <button id='btn'>按钮2</button>
    </div>)
  }

  componentDidMount() {
    const btn = document.getElementById('btn');
    // 2、将setState放入到原生DOM事件中(测试失败)
    btn.addEventListener('click', () => {
      this.setState({
        message: '孟美岐'
      })
      console.log('同步更新', this.state.message)
    })
  }

  /**
   * 1、setState一定是异步吗?
   *     - 在组件生命周期或React合成事件中,setState是异步
   *     - 在setTimeout或者原生dom事件中,setState是同步
   */
  changeMessage() {
    // 1、将setState放入到定时器中(测试失败)
    setTimeout(() => {
      this.setState({
        message: '姜贞羽'
      })
      console.log('同步更新', this.state.message)
    }, 0)
  }
}
3、setState数据的合并
import React from "react";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: '黄婷婷',
      name: '孟美岐'
    }
  }

  render() {
    return (<div>
      <h2>{this.state.message}</h2>
      <h2>{this.state.name}</h2>
      <button onClick={e => this.changeMessage()}>按钮</button>
    </div>)
  }

  changeMessage() {
    // 1、数据合并原理:Object.assign({}, this.state, {message: '姜贞羽'})
    this.setState({
      message: '姜贞羽'
    })
  }
}
4、setState本身的合并
import React from "react";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    }
  }

  render() {
    return (<div>
      <h2>{this.state.counter}</h2>
      <button onClick={e => this.increment()}>+1</button>
    </div>)
  }

  increment() {
    // 1、setState本身被合并
    /*
    this.setState({
      counter: this.state.counter + 1
    })
    this.setState({
      counter: this.state.counter + 1
    })
    this.setState({
      counter: this.state.counter + 1
    })*/
    // 2、setState合并时进行累加
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    })
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    })
    this.setState((prevState, props) => {
      return {
        counter: prevState.counter + 1
      }
    })
  }
}
5、React更新机制
* React的渲染流程
    - jsx -> 虚拟dom -> 真实dom
* React的更新流程
    - props/state改变 -> render函数重新执行 -> 产生新的dom树
      -> 新旧dom树进行diff -> 计算出差异进行更新 -> 更新到真实的dom
* diff算法
    - 同层节点之间相互比较,不会跨节点比较
    - 不同类型的节点,产生不同的树结构
    - 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定
6、列表中keys的作用
import React from "react";

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      movies: ['射雕英雄传', '神雕侠侣']
    }
  }

  /**
   * 一、对比不同类型的元素
   *     - 当节点为不同的元素,React会拆卸原有的树,并且建立起新的树
   *     - 当卸载一棵树时,对应的dom节点也会被销毁,组件实例将执行componentWillUnmount()方法
   *     - 当建立一棵新的树时,对应的dom节点会被创建以及插入到dom中,组件实例将执行
   *       componentWillMount()方法,紧接着componentDidMount()方法
   * 二、对比同一类型的元素
   *     - 当比对两个相同类型的React元素时,React会保留dom节点,仅比对及更新有改变的属性
   *     - 组件会保持不变,React会更新该组件的props,并且调用componentWillReceiveProps()
   *       和componentWillUpdate()方法
   *     - 下一步,调用render()方法,diff算法将在之前的结果以及新的结果中进行递归
   * 三、对子节点进行递归
   *     - 在默认条件下,当递归dom节点的子元素时,React会同时遍历两个子元素的列表;当产生差异时,
   *       生成一个mutation
   * 四、keys的优化
   *     - 当子元素拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素
   *     - key相同的子元素仅仅进行位移,不需要进行任何的修改
   *     - key不同的子元素则进行挂载或卸载操作
   * 五、key的注意事项
   *     - key应该是唯一的
   *     - key不要使用随机数
   *     - 使用index作为key,对性能是没有优化的
   */
  render() {
    return (<div>
      <ul>
        {this.state.movies.map(item => (<li key={item}>{item}</li>))}
      </ul>
      <button onClick={e => this.insertMovie()}>按钮</button>
    </div>)
  }

  insertMovie() {
    this.setState({
      movies: ['倚天屠龙记', ...this.state.movies]
    })
  }
}
7、render函数被调用
import React, {memo} from "react";

class Header extends React.PureComponent {
  render() {
    console.log('Header的render')
    return (<h3>{this.props.counter}</h3>)
  }
}

class Bodyer extends React.Component {
  render() {
    console.log('Bodyer的render')
    return (<div>
      <MemoLefter/>
      <Righter/>
    </div>)
  }
}

/**
 * 4、memo参数传入函数式组件,state/props不发生改变则不执行render(推荐)
 */
const MemoLefter = memo(function Lefter() {
  console.log('Lefter的render')
  return (<h3>左边</h3>)
})

function Righter() {
  console.log('Righter的render')
  return (<h3>右边</h3>)
}

/**
 * 3、类组件继承PureComponent,state/props不发生改变则不执行render(推荐)
 *     - 会在原型上增加一个属性(isPureReactComponent = true)
 */
class Footer extends React.PureComponent {
  render() {
    console.log('Footer的render')
    return (<h3>尾部</h3>)
  }
}

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    }
  }

  /**
   * 1、render函数被调用
   *     - 当调用App的render函数,所有子组件的render函数都会被重新调用
   *     - 事实上很多组件没必要重新render,调用render应该有一个前提(state/props发生改变)
   */
  render() {
    console.log('App的render')
    return (<div>
      <button onClick={e => this.increment()}>按钮</button>
      <Header counter={this.state.counter}/>
      <Bodyer/>
      <Footer/>
    </div>)
  }

  /**
   * 2、生命周期(简称SCU)
   *     - nextProps:修改后的props;nextState:修改后的state
   *     - 返回true:会调用render方法;返回false:不会调用render方法;默认返回true
   */
  shouldComponentUpdate(nextProps, nextState, nextContext) {
    return true
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}

八、知识补充和受控非受控组件

1、setState传递的数据需要是不可变的数据
import React from "react";

export default class App extends React.PureComponent {
  constructor(props) {
    super(props);
    // 引用类型
    this.state = {
      friends: [{
        name: '敌法师',
        age: 18
      }, {
        name: '赏金猎人',
        age: 19
      }, {
        name: '力丸',
        age: 20
      }]
    }
  }

  render() {
    return (<div>
      <h2>好友列表</h2>
      <ul>
        {this.state.friends.map((item, index) => {
          return (<li key={item.name}>
            姓名:{item.name}-
            年龄:{item.age}-
            <button onClick={e => this.incrementAge(index)}>age+1</button>
          </li>)
        })}
      </ul>
      <button onClick={e => this.insertData()}>添加数据</button>
    </div>)
  }

  /*
  shouldComponentUpdate(nextProps, nextState, nextContext) {
    if (this.state.friends !== nextState.friends) {
      return true
    }
    return false
  }*/

  insertData() {
    const newData = {name: '莱恩', age: 21}
    // 1、在开发中不要这样做
    /*
    this.state.friends.push(newData)
    this.setState({
      friends: this.state.friends
    })*/
    // 2、推荐做法
    const newFriends = [...this.state.friends]
    newFriends.push(newData)
    this.setState({
      friends: newFriends
    })
  }

  incrementAge(index) {
    const newFriends = [...this.state.friends]
    newFriends[index].age++
    this.setState({
      friends: newFriends
    })
  }
}
2、全局事件传递events
import React from "react";
import {EventEmitter} from "events"

// 事件总线:event bus
const eventBus = new EventEmitter();

class Home extends React.PureComponent {
  componentDidMount() {
    eventBus.addListener('sayHello', this.handleSayHelloListener)
  }

  componentWillUnmount() {
    eventBus.removeListener('sayHello', this.handleSayHelloListener)
  }

  handleSayHelloListener(...args) {
    console.log(args)
  }

  render() {
    return (<div>
      Home
    </div>)
  }
}

class Profile extends React.PureComponent {
  render() {
    return (<div>
      Profile
      <button onClick={e => this.emitEvent()}>点击了profile按钮</button>
    </div>)
  }

  emitEvent() {
    eventBus.emit('sayHello', 'hello home', 123)
  }
}

export default class App extends React.PureComponent {
  render() {
    return (<div>
      <Home/>
      <Profile/>
    </div>)
  }
}
3、如何使用ref
import React from "react";

/**
 * 1、在React的开发模式中,通常情况下不需要、也不建议直接操作dom原生,但是某些特殊的情况,
 *   确实需要获取到dom进行某些操作
 *     - 管理焦点,文本选择或媒体播放
 *     - 触发强制动画
 *     - 集成第三方dom库
 * 2、如何创建refs来获取对应的dom呢?目前有三种方式
 * 3、ref的值根据节点的类型而有所不同
 *     - 当ref属性用于html元素时,构造函数中使用React.createRef()
 *       创建的ref接收底层dom元素作为其current属性
 *     - 当ref属性用于自定义class组件时,ref对象接收组件的挂载实例作为其current属性
 *     - 你不能在函数组件上使用ref属性,因为他们没有实例
 * 4、函数式组件是没有实例的,所以无法通过ref获取他们的实例
 *     - 但是某些时候,我们可能想要获取函数式组件中的某个dom元素
 *     - 这个时候我们可以通过React.forwardRef,后面我们也会学习hooks中如何使用ref
 */
export default class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.titleRef = React.createRef()
    this.counterRef = React.createRef()
    this.titleEl = null
  }

  render() {
    return (<div>
      {/*<h2 ref=字符串/对象/函数>hello react</h2>*/}
      <h2 ref="titleRef">hello react</h2>
      {/*目前React推荐的方式*/}
      <h2 ref={this.titleRef}>hello react</h2>
      <h2 ref={arg => this.titleEl = arg}>hello react</h2>
      <button onClick={e => this.changeText()}>改变文本</button>
      <hr/>
      <Counter ref={this.counterRef}/>
      <button onClick={e => this.appBtnClick()}>App按钮</button>
    </div>)
  }

  appBtnClick() {
    this.counterRef.current.increment()
  }

  changeText() {
    // 1、使用方式一:字符串(不推荐,后续的更新会删除)
    this.refs.titleRef.innerHTML = "hello coderwhy"
    // 2、使用方式二:对象方式
    this.titleRef.current.innerHTML = "hello javascript"
    // 3、使用方式三:回调函数方式
    this.titleEl.innerHTML = "hello typescript"
  }
}

class Counter extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    }
  }

  render() {
    return (<div>
      <h2>当前计数:{this.state.counter}</h2>
      <button onClick={e => this.increment()}>+1</button>
    </div>)
  }

  increment() {
    this.setState({
      counter: this.state.counter + 1
    })
  }
}
4、认识受控组件
import React from "react";

export default class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      username: ""
    }
  }

  render() {
    return (<div>
      <form onSubmit={e => this.handleSubmit(e)}>
        <label htmlFor="username">
          用户:
          {/*受控组件*/}
          <input
            id="username"
            type="text"
            onChange={e => this.handleChange(e)}
            value={this.state.username}/>
        </label>
        <input type="submit" value="提交"/>
      </form>
    </div>)
  }

  handleSubmit(event) {
    event.preventDefault()
    console.log(this.state.username)
  }

  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
}
Element Value property Change callback New value in the callback
<input type="text" /> value="string" onChange event.target.value
<input type="checkbox" /> checked= onChange event.target.checked
<input type="radio" /> checked= onChange event.target.checked
<textarea /> value="string" onChange event.target.value
<select /> value="option value" onChange event.target.value
import React from "react";

export default class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      fruits: "banana"
    }
  }

  render() {
    return (<div>
      <form onSubmit={e => this.handleSubmit(e)}>
        <select
          name="fruits"
          onChange={e => this.handleChange(e)}
          value={this.state.fruits}>
          <option value="apple">苹果</option>
          <option value="banana">香蕉</option>
          <option value="orange">橘子</option>
        </select>
        <input type="submit" value="提交"/>
      </form>
    </div>)
  }

  handleSubmit(event) {
    event.preventDefault()
    console.log(this.state.fruits)
  }

  handleChange(event) {
    this.setState({
      fruits: event.target.value
    })
  }
}
5、非受控组件
import React from "react";

export default class App extends React.PureComponent {
  constructor(props) {
    super(props);
    this.usernameRef = React.createRef()
  }

  render() {
    return (<div>
      <form onSubmit={e => this.handleSubmit(e)}>
        <label htmlFor="username">
          用户:<input id="username" type="text" ref={this.usernameRef}/>
        </label>
        <input type="submit" value="提交"/>
      </form>
    </div>)
  }

  handleSubmit(event) {
    event.preventDefault()
    console.log(this.usernameRef.current.value)
  }
}
posted on 2022-11-08 09:54  一路繁花似锦绣前程  阅读(29)  评论(0编辑  收藏  举报