美文网首页
08react基础-react原理

08react基础-react原理

作者: 东邪_黄药师 | 来源:发表于2021-09-06 18:47 被阅读0次

    setState()更新数据

    • setState()更新数据是异步的
    • 注意:使用该语法,后面的setState不要依赖前面setState的值
    • 多次调用setState,只会触发一次render
    import React from 'react'
    import ReactDOM from 'react-dom'
    class App extends React.Component {
      state = {
        count: 1
      }
    
      handleClick = () => {
        // 此处,更新state
        // 注意:异步更新数据的!!!
        this.setState({
          count: this.state.count + 1
        })
        console.log('count:', this.state.count) // 1
        this.setState({
          count: this.state.count + 1 // 1 + 1
        })
        console.log('count:', this.state.count) // 1
      }
      render() {
        console.log('render')
        return (
          <div>
            <h1>计数器:{this.state.count}</h1>
            <button onClick={this.handleClick}>+1</button>
          </div>
        )
      }
    }
    ReactDOM.render(<App />, document.getElementById('root'))
    
    执行结果.png

    setState()推荐语法

    • 基础语法:
    this.setState((state, props) => {
    return {
    count: state.count + 1
      }
    })
    console.log(this.state.count) // 1
    
    • 完整代码:
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    /* 
      setState() 推荐语法
    */
    
    class App extends React.Component {
      state = {
        count: 1
      }
    
      handleClick = () => {
      
    
        // 推荐语法:
        // 注意:这种语法也是异步更新state的!
        this.setState((state, props) => {
          return {
            count: state.count + 1 // 1 + 1
          }
        })
        this.setState((state, props) => {
          console.log('第二次调用:', state)
          return {
            count: state.count + 1
          }
        })
        console.log('count:', this.state.count) // 1
      }
    
      render() {
        return (
          <div>
            <h1>计数器:{this.state.count}</h1>
            <button onClick={this.handleClick}>+1</button>
          </div>
        )
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'))
    
    执行结果.png

    setState()第二个参数

    • 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
    • 语法:setState(update[,callback])
    this.setState(
    (state, props) => {},
    () => {console.log('这个回调函数会在状态更新后立即执行')}
    )
    

    例子:

    this.setState(
    (state, props) => {},
    () => {
    document.title = '更新state后的标题:' + this.state.count
       }
    )
    

    具体代码:

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    /* 
      setState() callback
    */
    
    class App extends React.Component {
      state = {
        count: 1
      }
    
      handleClick = () => {
        this.setState(
          (state, props) => {
            return {
              count: state.count + 1
            }
          },
          // 状态更新后并且重新渲染后,立即执行:
          () => {
            console.log('状态更新完成:', this.state.count) // 2
            console.log(document.getElementById('title').innerText)
            document.title = '更新后的count为:' + this.state.count
          }
        )
        console.log(this.state.count) // 1
      }
    
      render() {
        return (
          <div>
            <h1 id="title">计数器:{this.state.count}</h1>
            <button onClick={this.handleClick}>+1</button>
          </div>
        )
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'))
    

    JSX语法的转化过程

    image.png
    • JSX仅仅是createElement() 方法的语法糖(简化语法)
    • JSX语法被 @babel/preset-react 插件编译为createElement() 方法
    • React 元素: 是一个对象,用来描述你希望在屏幕上看到的内容


      image.png

    组件更新机制

    • setState() 的两个作用

      • 修改state
      • 更新组件
    • 过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子组件)


      image.png
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    /* 
      组件更新机制
    */
    
    import './index.css'
    
    // 根组件
    class App extends React.Component {
      state = {
        color: '#369'
      }
    
      getColor() {
        return Math.floor(Math.random() * 256)
      }
    
      changeBG = () => {
        this.setState(() => {
          return {
            color: `rgb(${this.getColor()}, ${this.getColor()}, ${this.getColor()})`
          }
        })
      }
    
      render() {
        console.log('根组件')
        return (
          <div className="app" style={{ backgroundColor: this.state.color }}>
            <button onClick={this.changeBG}>根组件 - 切换颜色状态</button>
            <div className="app-wrapper">
              <Parent1 />
              <Parent2 />
            </div>
          </div>
        )
      }
    }
    
    // ------------------------左侧---------------------------
    
    class Parent1 extends React.Component {
      state = {
        count: 0
      }
    
      handleClick = () => {
        this.setState(state => ({ count: state.count + 1 }))
      }
      render() {
        console.log('左侧父组件')
        return (
          <div className="parent">
            <h2>
              左侧 - 父组件1
              <button onClick={this.handleClick}>点我({this.state.count})</button>
            </h2>
            <div className="parent-wrapper">
              <Child1 />
              <Child2 />
            </div>
          </div>
        )
      }
    }
    
    class Child1 extends React.Component {
      render() {
        console.log('左侧子组件 - 1')
        return <div className="child">子组件1-1</div>
      }
    }
    class Child2 extends React.Component {
      render() {
        console.log('左侧子组件 - 2')
        return <div className="child">子组件1-2</div>
      }
    }
    
    // ------------------------右侧---------------------------
    
    class Parent2 extends React.Component {
      state = {
        count: 0
      }
    
      handleClick = () => {
        this.setState(state => ({ count: state.count + 1 }))
      }
    
      render() {
        console.log('右侧父组件')
        return (
          <div className="parent">
            <h2>
              右侧 - 父组件2
              <button onClick={this.handleClick}>点我({this.state.count})</button>
            </h2>
            <div className="parent-wrapper">
              <Child3 />
              <Child4 />
            </div>
          </div>
        )
      }
    }
    
    class Child3 extends React.Component {
      render() {
        console.log('右侧子组件 - 1')
        return <div className="child">子组件2-1</div>
      }
    }
    class Child4 extends React.Component {
      render() {
        console.log('右侧子组件 - 2')
        return <div className="child">子组件2-2 </div>
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'))
    
    更新子组件时.png
    更新根组件时..png

    组件性能优化

    • 1.减轻state
    • 减轻state:只存储跟组件渲染相关的数据(比如:count/ 列表数据 /loading等)
    • 注意:不用做渲染的数据不要放在state中
    • 对于这种需要在多个方法中用到的数据,应该放到this中
    class Hello extends Component {
    componentDidMount() {
    // timerId存储到this中,而不是state中
    this.timerId = setInterval(() => {}, 2000)
      }
    componentWillUnmount() {
    clearInterval(this.timerId)
       }
    render() { … }
    }
    
    • 2.避免不必要的重新渲染
    • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
    • 问题:子组件没有任何变化时也会重新渲染
    • 如果避免不必要的重新渲染?
    • 解决方式:使用钩子函数shouldComponentUpdate(nextProps, nextState)
      • 在这个函数中,nextPropsnextState是最新的状态以及属性
    • 作用:这个函数有返回值,如果返回true,代表需要重新渲染,如果返回false,代表不需要重新渲染
    • 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate => render)
    class Hello extends Component {
    shouldComponentUpdate() {
    // 根据条件,决定是否重新渲染组件
    return false
    }
    render() {…}
    }
    

    随机数案例 如果随机获取的数值和之上一次一样不更新组件否则就更新

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    /* 
      组件性能优化:
    */
    
    // 生成随机数
    class App extends React.Component {
      state = {
        number: 0
      }
    
      handleClick = () => {
        this.setState(() => {
          return {
            number: 1
          }
        })
      }
    
      // 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染
      shouldComponentUpdate(nextProps, nextState) {
        console.log('最新状态:', nextState, ', 当前状态:', this.state)
    
        return nextState.number !== this.state.number
    
        // if (nextState.number !== this.state.number) {
        //   return true
        // }
        // return false
    
        // if (nextState.number === this.state.number) {
        //   return false
        // }
        // return true
      }
    
      render() {
        console.log('render')
        return (
          <div>
            <h1>随机数:{this.state.number}</h1>
            <button onClick={this.handleClick}>重新生成</button>
          </div>
        )
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'))
    
    • nextprops:
    import React from 'react'
    import ReactDOM from 'react-dom'
    
    /* 
      组件性能优化:
    */
    
    // 生成随机数
    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)
      //   return nextState.number !== this.state.number
      // }
    
      render() {
        // console.log('render')
        return (
          <div>
            <NumberBox number={this.state.number} />
            <button onClick={this.handleClick}>重新生成</button>
          </div>
        )
      }
    }
    
    class NumberBox extends React.Component {
      shouldComponentUpdate(nextProps) {
        console.log('最新props:', nextProps, ', 当前props:', this.props)
        // 如果前后两次的number值相同,就返回false,不更新组件
        return nextProps.number !== this.props.number
    
        // 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'))
    

    纯组件

    作用以及使用

    • 纯组件: PureComponentReact.Component 功能相似
    • 区别: PureComponent 内部自动实现了 shouldComponentUpdate钩子,不需要手动比较
    • 原理:纯组件内部通过分别比对前后两次 props和state的值,来决定是否重新渲染组件
    class Hello extends React.PureComponent {
    render() {
    return (
    <div>纯组件</div>
         )
      }
    }
    
    • 实现原理
    • 说明:纯组件内部的对比是 shallow compare(浅层对比)
    • 对于值类型来说:比较两个值是否相同
    const obj = { number: 0 }
    const newObj = obj
    newObj.number = 2
    console.log(newObj === obj) // true
    
    • 引用类型:只比对对象的引用地址是否相同
    state = { obj: { number: 0 } }
    // 错误做法
    state.obj.number = 2
    setState({ obj: state.obj })
    // PureComponent内部比较:
    最新的state.obj === 上一次的state.obj // true,不重新渲染组件
    
    • 注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据
    // 正确!创建新数据
    const newObj = {...state.obj, number: 2}
    setState({ obj: newObj })
    // 正确!创建新数据
    // 不要用数组的push / unshift 等直接修改当前数组的的方法
    // 而应该用 concat 或 slice 等这些返回新数组的方法
    this.setState({
    list: [...this.state.list, {新数据}]
    })
    

    案例:

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    /* 
      组件性能优化:
    */
    
    // 引用类型:
    const obj = { number: 0 }
    const newObj = obj
    newObj.number = 2
    console.log(newObj === obj) // true
    
    // 生成随机数
    class App extends React.PureComponent {
      state = {
        obj: {
          number: 0
        }
      }
    
      handleClick = () => {
        // 正确做法:创建新对象
        const newObj = { ...this.state.obj, number: Math.floor(Math.random() * 3) }
        this.setState(() => {
          return {
            obj: newObj
          }
        })
    
        // 错误演示:直接修改原始对象中属性的值
        /* const newObj = this.state.obj
        newObj.number = Math.floor(Math.random() * 3)
    
        this.setState(() => {
          return {
            obj: newObj
          }
        }) */
      }
    
      render() {
        console.log('父组件重新render')
        return (
          <div>
            <h1>随机数:{this.state.obj.number}</h1>
            <button onClick={this.handleClick}>重新生成</button>
          </div>
        )
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'))
    

    虚拟 DOM 和 Diff 算法

    • React更新视图的思想是:只要state变化就重新渲染视图
    • 特点:思路非常清晰
    • 问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染吗? 不是这样的
    • 理想状态:部分更新,只更新变化的地方
    • React运用的核心点就是 虚拟DOM 配合 Diff 算法

    虚拟DOM

    虚拟 DOM:本质上就是一个 JS 对象,用来描述你希望在屏幕上看到的内容(UI)。


    image.png

    Diff算法

    执行过程

    • 初次渲染时,React会根据初始化的state(model),创建一个虚拟DOM对象(树)
    • 根据虚拟DOM生成真正的DOM,渲染到页面
    • 当数据变化后(setState()),会重新根据新的数据,创建新的虚拟DOM对象(树)
    • 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
    • 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面
    image.png

    代码演示:

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    /* 
      虚拟DOM 和 Diff算法
    */
    
    // 生成随机数
    class App extends React.PureComponent {
      state = {
        number: 0
      }
    
      handleClick = () => {
        this.setState(() => {
          return {
            number: Math.floor(Math.random() * 2)
          }
        })
      }
    
      // render方法调用并不意味着浏览器中的重新渲染!!!
      // render方法调用仅仅说明要进行diff
      render() {
        const el = (
          <div>
            <h1>随机数:</h1>
            <p>{this.state.number}</p>
            <button onClick={this.handleClick}>重新生成</button>
          </div>
        )
        console.log(el)
    
        return el
      }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'))
    
    image.png

    相关文章

      网友评论

          本文标题:08react基础-react原理

          本文链接:https://www.haomeiwen.com/subject/kuvswltx.html