zoukankan      html  css  js  c++  java
  • React 【State进阶】通过条件判断优化渲染、使用不可变数据、单一数据源、状态提升、使用无状态组件

    目录:

    1. 通过条件判断优化渲染

          shouldComponentUpdate

          PureComponent(推荐使用)

    2. 使用不可变数据

    3. 单一数据源

    4. 状态提升

    5. 使用无状态组件

    一、通过条件判断优化渲染

     例 子 

    目前的页面表现如下,点击 “删除” 按钮之后在控制台会输出商品 id。

    (例子相关笔记:https://www.cnblogs.com/xiaoxuStudy/p/13327924.html#four )

    import React, { Component } from 'react';
    import ListItem from './components/listItem'
    
    class App extends Component {
        constructor( props ){
            super(props)
            this.state = {
                listData : [
                    {
                        id: 1,
                        name: '红苹果',
                        price: 2
                    },
                    {
                        id: 2,
                        name: '青苹果',
                        price: 3
                    },
                ]
            }
        }
        renderList(){
            return this.state.listData.map( item => {
                return <ListItem key={item.id} data={ item }  onDelete={this.handleDelete} />
            })
        }
        handleDelete = (id) => {
            console.log( 'id:', id );
        }
        render() { 
            return(
                <div className="container">
                    { this.state.listData.length === 0 && <div className="text-center">购物车是空的</div> }
                    { this.renderList() }
                </div>
            )
        }
    }
     
    export default App;
    父组件 App.js
    import React, { Component } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends Component {
        constructor(props){
            super(props)
            this.state = {
                count : 0
            }
        }
        handleDecrease = () => {
            this.setState({
                count : this.state.count - 1
            })
        }
        handleIncrease = () => {
            this.setState({
                count : this.state.count + 1
            })
        }
        render() { 
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}>
                        <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button>
                        <span className={ cls('digital') }>{this.state.count}</span>
                        <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.state.count}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            className="btn btn-danger btn-sm" 
                            type="button"
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    子组件 listItem.jsx

    下面在以上代码的基础上实现点击 “删除” 按钮之后该商品被删除的功能。

    App.js:

      当点击删除按钮时,会触发 listItem.jsx 的点击事件执行 listItem.jsx 中的从 App.js 传入的 onDelete 并且传入参数 id,在父组件中 onDelete 的值是函数 handleDelete。所以,点击按钮就会执行父组件中的 handleDelete 函数。如果想要实现点击 “ 删除 ” 按钮商品被删除,需要在父组件中去除当前选定的商品,以上已经实现了从子组件向父组件传递商品 id,以下只需要通过修改父组件的 state 来删除选定的商品。

      在 handleDelete 中,定义 listData 数组,使用 filter 方法去返回一个新的数组,过滤条件是 item 的 id 不等于传入的 id。使用 setState 方法将新创建的数组传给原来的数组 listData。

    import React, { Component } from 'react';
    import ListItem from './components/listItem'
    
    class App extends Component {
        constructor( props ){
            super(props)
            this.state = {
                listData : [
                    {
                        id: 1,
                        name: '红苹果',
                        price: 2
                    },
                    {
                        id: 2,
                        name: '青苹果',
                        price: 3
                    },
                ]
            }
        }
        renderList(){
            return this.state.listData.map( item => {
                return <ListItem key={item.id} data={ item }  onDelete={this.handleDelete} />
            })
        }
        handleDelete = (id) => {
            const listData = this.state.listData.filter( item => item.id !== id )
            this.setState({
                listData
            })
        }
        render() { 
            return(
                <div className="container">
                    { this.state.listData.length === 0 && <div className="text-center">购物车是空的</div> }
                    { this.renderList() }
                </div>
            )
        }
    }
     
    export default App;
    App.js
        handleDelete = (id) => {
            const _list = this.state.listData.filter( item => item.id !== id )
            this.setState({
                listData : _list
            })
        }
    handleDelete另一种写法

    页面表现:

      点击 “删除” 按钮之后,商品消失。

    shouldComponentUpdate

      使用 shouldComponentUpdate 可以有效地去避免不必要的 render 方法的执行。

      对于 React 的 render 方法,默认情况下,不管传入的 state 或 props 是否变化,都会触发重新渲染,这里重新渲染指的是虚拟 DOM 的渲染,不是真实 DOM 的重绘。即使真实 DOM 不变化,当 React 应用足够庞大的时候,重新去触发 render 也是一笔不小的开销,这个问题可以使用 shouldComponentUpdate 解决。

      例 子 :了解 render 的执行 

      在子组件 listItem.jsx 的 render 方法中添加一条语句做标记,每次执行了 render 方法都会在控制台打印出 “item is rendering”。

    页面初始化时控制台输出 2 次 "item is rendering",因为有 2 个商品

     

    点击其中一个 “删除” 按钮之后控制台输出 1 次 ”item is rendering“,因为有 1 个商品。但是留下的商品的任何数据都没有发生变化与原来一致。

    将子组件 listItem.jsx 中的方法 handleIncrease 中的 count 修改为 3,使得点击页面上 “ + ” 按钮的值 count 的值为 3,传入 render 的 count 值不变。

    在页面上点击几下 "+" 按钮,count 值不变一直为 3 ,但是点击 “ + ” 按钮几次 render 就被执行几次。

      例子:了解 shouldComponentUpdate 的 this.props、this.state、nextProps、nextState  

      shouldComponentUpdate 是重新渲染时 render 方法执行前被调用的,它接受 2 个参数,第一个是 nextProps,第二个是 nextState。nextProps 代表下一个 props , nextState 代表下一个 state。

      在 listItem.jsx 里使用 shouldComponentUpdata。将目前的 props、下一个 props、目前的 state、下一个 state 打印出来看看。

    import React, { Component } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends Component {
        constructor(props){
            super(props)
            this.state = {
                count : 0
            }
        }
        handleDecrease = () => {
            this.setState({
                count : this.state.count - 1
            })
        }
        handleIncrease = () => {
            this.setState({
                count : 3
            })
        }
        shouldComponentUpdate(nextProps, nextState){
            console.log('props', this.props, nextProps);
            console.log('state', this.state, nextState)
        }
        render() { 
            console.log('item is rendering');
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}>
                        <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button>
                        <span className={ cls('digital') }>{this.state.count}</span>
                        <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.state.count}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            className="btn btn-danger btn-sm" 
                            type="button"
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    listItem.jsx

    页面表现:

      点击 “删除” 按钮,可以看到目前的 props、下一个 props、目前的 state、下一个 state。

    可以分别展开查看目前的 props、下一个 props、目前的 state、下一个 state。

      例子:使用 shouldComponentUpdate 阻止 render 重新渲染  

    下面实现:传入的 state 不变化时,不触发重新渲染。点击 “+” 按钮触发的事件是修改 count 值为 3 。

    import React, { Component } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends Component {
        constructor(props){
            super(props)
            this.state = {
                count : 0
            }
        }
        handleDecrease = () => {
            this.setState({
                count : this.state.count - 1
            })
        }
        handleIncrease = () => {
            this.setState({
                count : 3
            })
        }
        shouldComponentUpdate(nextProps, nextState){
            if( this.state.count === nextState.count ) return false
            return true
        }
        render() { 
            console.log('item is rendering');
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}>
                        <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button>
                        <span className={ cls('digital') }>{this.state.count}</span>
                        <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.state.count}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            className="btn btn-danger btn-sm" 
                            type="button"
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    listItem.jsx

    页面表现:

      因为点击 “ + ” 按钮触发的事件修改 count 为 3 ,state 值不发生变化,所以 render 没有再执行。

    下面实现:当前 props 与 下一 props 相同时,不触发重新渲染。

    注意:不能直接判断当前 props 跟下一个 props,因为他们是两个不同的引用,所以要通过判断 props 的某些属性来判断当前 props 与下一 props 是否相同。本例通过 id 进行判断。

    import React, { Component } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends Component {
        constructor(props){
            super(props)
            this.state = {
                count : 0
            }
        }
        handleDecrease = () => {
            this.setState({
                count : this.state.count - 1
            })
        }
        handleIncrease = () => {
            this.setState({
                count : 3
            })
        }
        shouldComponentUpdate(nextProps, nextState){
            if( this.props.id === nextProps.id ) return false
            return true
        }
        render() { 
            console.log('item is rendering');
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}>
                        <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button>
                        <span className={ cls('digital') }>{this.state.count}</span>
                        <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.state.count}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            className="btn btn-danger btn-sm" 
                            type="button"
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    listItem.jsx

    页面表现:

    点击 “ 删除 ” 按钮将商品删光,从控制台可以看出删除商品没有执行 render。

    PureComponent

      使用 PureComponnet 能达到跟使用 shouldComponentUpdate 相同效果。

      例 子  

    import React, { PureComponent } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends PureComponent {
        constructor(props){
            super(props)
            this.state = {
                count : 0
            }
        }
        handleDecrease = () => {
            this.setState({
                count : this.state.count - 1
            })
        }
        handleIncrease = () => {
            this.setState({
                count : 3
            })
        }
        render() { 
            console.log('item is rendering');
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}>
                        <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button>
                        <span className={ cls('digital') }>{this.state.count}</span>
                        <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.state.count}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            className="btn btn-danger btn-sm" 
                            type="button"
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    listItem.jsx

    二、使用不可变数据

      当使用 setState 的时候,需要使用不可变数据。(相关笔记:https://www.cnblogs.com/xiaoxuStudy/p/13336594.html#three

      例子 :没有使用不可变数据造成的问题 

      给页面添加一个 “减一个” 按钮,理想中的效果是点击按钮执行函数 handleMount 在页面上减少一个商品。

    import React, { PureComponent } from 'react';
    import ListItem from './components/listItem'
    
    class App extends PureComponent {
        constructor( props ){
            super(props)
            this.state = {
                listData : [
                    {
                        id: 1,
                        name: '红苹果',
                        price: 2
                    },
                    {
                        id: 2,
                        name: '青苹果',
                        price: 3
                    },
                ]
            }
        }
        renderList(){
            return this.state.listData.map( item => {
                return <ListItem key={item.id} data={ item }  onDelete={this.handleDelete} />
            })
        }
        handleDelete = (id) => {
            const listData = this.state.listData.filter( item => item.id !== id )
            this.setState({
                listData
            })
        }
        handleAmount = () => {
            const _list = this.state.listData
            _list.pop()
            this.setState({
                listData : _list
            })
        }
        render() { 
            return(
                <div className="container">
                    <button onClick={this.handleAmount} className="btn btn-primary">减一个</button>
                    { this.state.listData.length === 0 && <div className="text-center">购物车是空的</div> }
                    { this.renderList() }
                </div>
            )
        }
    }
     
    export default App;
    App.js

    页面表现:

      点击 “减一个” 按钮之后,state 值余下 1 个商品,但是页面并没有更新,仍然存在 2 个商品。这就是没有使用不可变数据造成的问题。

    解决方法:

      使用 concat 方法生成新的数组,然后在新的数组上减一,并将新数组赋予 state 值 listData。

    页面表现:

    点击 “减一个” 之后,state 值余下 1 个商品,页面也更新了,存在 1 个商品。

    为何使用不可变数据

      Undefined、Null、Boolean、Number 和 String 都是基本类型,它们是按值访问的,保存在栈中。Object、Array、Function是引用类型,是按引用访问的,保存在堆中。JS 不允许直接访问内存中的位置,在操作对象时,实际上是操作对象的引用而不是实际的对象。(相关笔记:https://www.cnblogs.com/xiaoxuStudy/p/12509729.html

      不同的引用指向堆内存的同一个对象,所以,当我们去做判断的时候,因为是在内存中的同一个对象,所以会判断为 true。当使用了 PureComponent ,UI 并不会进行更新。

    const _list = this.state.listData
    

      只有使用了不可变数据去生成一个新对象,这时候新对象与原来的 state 引用的是不同的对象,这时才可以进行正确的比较。不过,这个比较是浅复制的比较,会比较每一个 key 是否两者都有,对于数据有深层次嵌套的比较一般会使用 JSON.stringify 和 JSON.parse,或者会使用类似 Immutable 这样的 JS 库管理不可变的数据。

    const _list = this.state.listData.concat([])
    

       所以,在实际的 React 应用中,尽可能地使用 PureComponent 去优化 React  应用,同时,也要去使用不可变数据去修改 state 值或者 props 值保证数据引用不出错。使用不可变数据可以避免引用带来的副作用,使整个程序的数据变得易于管理。

    三、单一数据源

      所有相同的子组件应该有一个主状态,然后使用这个状态以 props 形式传递给子组件。

      例 子 : 没有使用单一数据源会造成的问题  

      给购物车添加一个重置按钮,当点击 “重置” ,购物车的所有商品的数量都变为 0。

    父组件 App.js:

      给 listData 增加一个 value 值,模拟从后端传过来的购物车数量的初始值。

      添加一个 “重置” 按钮,点击按钮调用 handleReset。

      在 handleReset 里使用 map 方法创建新数组,新数组的 value 值为 0 ,使用 setState 方法将新数组赋给 listData。

    import React, { PureComponent } from 'react';
    import ListItem from './components/listItem'
    
    class App extends PureComponent {
        constructor( props ){
            super(props)
            this.state = {
                listData : [
                    {
                        id: 1,
                        name: '红苹果',
                        price: 2,
                        value: 4
                    },
                    {
                        id: 2,
                        name: '青苹果',
                        price: 3,
                        value: 2
                    },
                ]
            }
        }
        renderList(){
            return this.state.listData.map( item => {
                return <ListItem key={item.id} data={ item }  onDelete={this.handleDelete} />
            })
        }
        handleDelete = (id) => {
            const listData = this.state.listData.filter( item => item.id !== id )
            this.setState({
                listData
            })
        }
        handleReset = () => {
            const _list = this.state.listData.map( item => {
                const _item = {...item}
                _item.value = 0
                return _item
            })
            this.setState({
                listData : _list
            })
        }
        render() { 
            return(
                <div className="container">
                    <button onClick={this.handleReset} className="btn btn-primary">重置</button>
                    { this.state.listData.length === 0 && <div className="text-center">购物车是空的</div> }
                    { this.renderList() }
                </div>
            )
        }
    }
     
    export default App;
    父组件 App.js

    子组件 listItem.jsx:

      将 state 值 count 初始化为 value 值

    import React, { PureComponent } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends PureComponent {
        constructor(props){
            super(props)
            this.state = {
                count : this.props.data.value
            }
        }
        handleDecrease = () => {
            this.setState({
                count : this.state.count - 1
            })
        }
        handleIncrease = () => {
            this.setState({
                count : 3
            })
        }
        render() { 
            console.log('item is rendering');
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.state.count ? '' : '-s'}`}>
                        <button onClick={ this.handleDecrease } type="button" className="btn btn-primary">-</button>
                        <span className={ cls('digital') }>{this.state.count}</span>
                        <button onClick={ this.handleIncrease } type="button" className="btn btn-primary">+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.state.count}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            className="btn btn-danger btn-sm" 
                            type="button"
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    子组件 listItem.jsx

    页面表现:

      点击 “重置” 按钮之后,页面并没有发生变化,查看控制台 Component 处,可以看到在父组件的 state 里 value 值已经被设置为了 0。

       子组件 listItem 传入的 props 的 value 也是 0,然而 state 还是原来的数据。如果点击 “ + ”“ - ” 按钮,state 中的 count 值会变,如果点击 “重置” 按钮,state 中的 count 值不会变。

      以上,就是一个没有使用单一数据源造成问题的例子。

    单一数据源原则

      使用单一数据源,当主状态的任何一部分发生改变,它会自动更新以这部分为 props 的子组件,这种变化是从上而下传达到子组件的。

      那要怎么做呢?首先,将子组件的 count 状态去掉,然后将所有的数据通过父组件传递给子组件,这时候子组件也被称为受控组件,在子组件中绑定 props 传入的函数,让父组件去操作数据。

      例子:使用单一数据源  

      在上面例子的基础上进行改动。

    子组件 listItem.jsx:

      一般在设计比较好的组件中,比较少用到 state,一般只有一个 render 方法。

      将 constructor 创建的 state 删除。

      将 render 方法中用到的 state 都改为 props 传入的形式,将所有的数据通过父组件传递给子组件。

      在子组件 react 元素上,绑定 props 传入的函数 onIncrese 跟 onDecrease 并带入参数。(可参考 onDelete 相关笔记: https://www.cnblogs.com/xiaoxuStudy/p/13327924.html#four )

    import React, { PureComponent } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends PureComponent {
        render() { 
            console.log('item is rendering');
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.props.data.value ? '' : '-s'}`}>
                        <button 
                            onClick={()=>{this.props.onDecrease(this.props.data.id)}}  
                            type="button" className="btn btn-primary"
                        >-</button>
                        <span className={ cls('digital') }>{this.props.data.value}</span>
                        <button 
                            onClick={()=>{this.props.onIncrease(this.props.data.id)}}  
                            type="button" className="btn btn-primary"
                        >+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.props.data.value}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            type="button" className="btn btn-danger btn-sm" 
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    子组件 listItem.jsx

    父组件 App.js:

      在父组件定义好事件处理函数 handleIncrease 跟 handleDecrease,并通过 props 向子组件传递。

    import React, { PureComponent } from 'react';
    import ListItem from './components/listItem'
    
    class App extends PureComponent {
        constructor( props ){
            super(props)
            this.state = {
                listData : [
                    {
                        id: 1,
                        name: '红苹果',
                        price: 2,
                        value: 4
                    },
                    {
                        id: 2,
                        name: '青苹果',
                        price: 3,
                        value: 2
                    },
                ]
            }
        }
        renderList(){
            return this.state.listData.map( item => {
                return <ListItem 
                            key={item.id} 
                            data={ item }  
                            onDelete={this.handleDelete} 
                            onIncrease={this.handleIncrease}
                            onDecrease={this.handleDecrease}
                        />
            })
        }
        handleDelete = (id) => {
            const listData = this.state.listData.filter( item => item.id !== id )
            this.setState({
                listData
            })
        }
        handleReset = () => {
            const _list = this.state.listData.map( item => {
                const _item = {...item}
                _item.value = 0
                return _item
            })
            this.setState({
                listData : _list
            })
        }
        handleIncrease = (id) => {
            const _data = this.state.listData.map( item => {
                if( item.id === id ){
                    const _item = {...item}
                    _item.value++
                    return _item
                }else{
                    return item
                }
            })
            this.setState({
                listData : _data
            })
    
        }
        handleDecrease = (id) => {
            const _data = this.state.listData.map( item => {
                if( item.id === id ){
                    const _item = {...item}
                    _item.value--
                    if( _item.value < 0 ) _item.value = 0
                    return _item
                }else{
                    return item
                }
            })
            this.setState({
                listData : _data
            })
        }
        render() { 
            return(
                <div className="container">
                    <button onClick={this.handleReset} className="btn btn-primary">重置</button>
                    { this.state.listData.length === 0 && <div className="text-center">购物车是空的</div> }
                    { this.renderList() }
                </div>
            )
        }
    }
     
    export default App;
    父组件 App.js

    页面表现:

    点击 “重置” 之后,商品数量变为 0

    随意点击加减按钮,点 “ + ” 会数量加 1,点 “ - ” 数量会减 1 ,但是数量不会变为负数

      将 listItem.jsx 的 state 去除,子组件 listItem.jsx 的数据全部接受于父组件 App.js,这时 listItem.jsx 也被称为受控组件。所有,当开始设计应用结构时,应该尽量组织好组件之间的框架和数据传递的方式,尽可能采用单一数据源的方式,将子组件需要的数据都由父组件传入。

    四、状态提升

       多个组件需要对同一个数据的变化做出反应的时候,也就是操作同一个源数据的时候,建议将共享状态提升到最近的共同父组件去。

      例 子  

    需求:购物车页面包含导航栏跟商品列表。导航栏显示总商品数、重置按钮,商品列表可以实现加减商品数量、删除商品。

    效果预览:

    购物车应用结构:

      App:是公用的父组件。

      NavBar:是 App 的子组件,负责头部导航栏的内容。

      ListPage:是 App 的子组件,负责商品列表的渲染。

      ListItem:是 ListPage 的子组件,负责每个具体商品的展示。

      App 存储数据,通过向下传递数据的方式传入 props 将数据传给 NavBar、ListPage,ListPage 再将数据传给 ListItem,这样的形式就叫做状态提升。状态提升主要是用来处理父组件和子组件的数据传递,它可以让数据流动自顶向下,单向流动。所有组件的数据都是来自于它们的父辈组件,本例中是 App,父辈组件 App 统一存储和修改数据然后将其传入子组件中,子组件调用事件处理函数来使用父组件的方法,控制 state 数据的更新,从而完成整个应用的更新。

      下面通过代码理解状态提升。

    import React, { PureComponent } from 'react';
    import Navbar from "./components/navbar"
    import ListPage from './components/listPage'
    
    class App extends PureComponent {
        constructor( props ){
            super(props)
            this.state = {
                listData : [
                    {
                        id: 1,
                        name: '红苹果',
                        price: 2,
                        value: 4
                    },
                    {
                        id: 2,
                        name: '青苹果',
                        price: 3,
                        value: 2
                    },
                ]
            }
        }
        handleDelete = (id) => {
            const listData = this.state.listData.filter( item => item.id !== id )
            this.setState({
                listData
            })
        }
        handleReset = () => {
            const _list = this.state.listData.map( item => {
                const _item = {...item}
                _item.value = 0
                return _item
            })
            this.setState({
                listData : _list
            })
        }
        handleIncrease = (id) => {
            const _data = this.state.listData.map( item => {
                if( item.id === id ){
                    const _item = {...item}
                    _item.value++
                    return _item
                }else{
                    return item
                }
            })
            this.setState({
                listData : _data
            })
        }
        handleDecrease = (id) => {
            const _data = this.state.listData.map( item => {
                if( item.id === id ){
                    const _item = {...item}
                    _item.value--
                    if( _item.value < 0 ) _item.value = 0
                    return _item
                }else{
                    return item
                }
            })
            this.setState({
                listData : _data
            })
        }
        render() { 
            return(
                <>
                    <Navbar 
                        onReset = {this.handleReset}
                        total = {this.state.listData.length}
                    />
                    <ListPage 
                        data = {this.state.listData}
                        handleDecrease = {this.handleDecrease}
                        handleIncrease = {this.handleIncrease}
                        handleDelete = {this.handleDelete}
                    />
                </>
            )
        }
    }
     
    export default App;
    App.js

    import React, {PureComponent} from 'react';
    
    class NavBar extends PureComponent {
        render(){
            return(
                <nav className="navbar navbar-expand-lg navbar-light bg-light">
                    <div className="container">
                        <div className="wrap">
                            <span className="title">NAVBAR</span>
                            <span className="badge badge-pill badge-primary ml-2 mr-2">
                                {this.props.total}
                            </span>
                            <button
                                onClick={this.props.onReset}
                                className="btn btn-outline-success my-2 my-sm-0 fr"
                                type="button"
                            >
                                Reset
                            </button>
                        </div>
                    </div>    
                </nav>
            );
        }
    }
    
    export default NavBar;
    navbar.jsx

    import React, { PureComponent } from 'react';
    import ListItem from './listItem'
    
    class ListPage extends PureComponent {
        renderList(){
            return this.props.data.map( item => {
                return <ListItem 
                            key={item.id} 
                            data={ item }  
                            onDelete={this.props.handleDelete} 
                            onIncrease={this.props.handleIncrease}
                            onDecrease={this.props.handleDecrease}
                        />
            })
        }
        render() { 
            return ( 
                <div className="container">
                    { this.props.data.length === 0 && <div className="text-center">购物车是空的</div> }
                    { this.renderList() }
                </div>
            );
        }
    }
     
    export default ListPage;
    listPage.jsx

    import React, { PureComponent } from 'react';
    import style from './listItem.module.css';
    import classnames from 'classnames/bind'
    
    const cls = classnames.bind(style);
    
    class ListItem extends PureComponent {
        render() { 
            console.log('item is rendering');
            return ( 
                <div className="row mb-3">
                    <div className="col-6 themed-grid-col">
                        <span className={ cls('title', 'list-title') }>
                            {this.props.data.name}
                        </span>
                    </div>
                    <div className="col-1 themed-grid-col">¥{this.props.data.price}</div>
                    <div className={`col-2 themed-grid-col${this.props.data.value ? '' : '-s'}`}>
                        <button 
                            onClick={()=>{this.props.onDecrease(this.props.data.id)}}  
                            type="button" className="btn btn-primary"
                        >-</button>
                        <span className={ cls('digital') }>{this.props.data.value}</span>
                        <button 
                            onClick={()=>{this.props.onIncrease(this.props.data.id)}}  
                            type="button" className="btn btn-primary"
                        >+</button>
                    </div>
                    <div className="col-2 themed-grid-col">¥{this.props.data.price * this.props.data.value}</div>
                    <div className="col-1 themed-grid-col">
                        <button 
                            onClick={()=>{this.props.onDelete(this.props.data.id)}} 
                            type="button" className="btn btn-danger btn-sm" 
                        >删除
                        </button>
                    </div>
                </div>
            );
        }
    }
    
    export default ListItem;
    listItem.jsx

    页面表现:

    加、减、删除按钮都能正常使用。下面测试导航栏的 “Reset” 按钮,点击 “Reset” 按钮之后商品数量都变为 0 了。

    测试导航栏的显示的商品总数,点击 “删除” 按钮之后,商品总数变为 1。

    总结:当子组件都要控制同样一个数据源的时候,需要将整个数据提升到它们共同的父组件中,然后再通过父组件赋值的方式传递给子组件,并由父组件统一地对数据进行管理与存储。

    五、使用无状态组件

    Stateful 和 Stateless 的区别

    1. Stateful

      有状态组件也被称为类组件、容器组件。用 class 创建的组件是可以使用 state 状态的,同时,它也是一个像容器一样可以包含其它的无状态组件的组件。

    2. Stateless

      无状态组件也被称为函数组件、展示组件。它是通过纯函数的方法来定义的,而它所有的数据都是来自于它的父组件,它仅起到一个展示的作用。

    何时使用何种组件

      尽可能通过状态提升原则,将需要的状态提取到父组件中,而其他的组件使用无状态组件编写。

      尽可能使用无状态组件,尽少使用状态组件。因为无状态组件会使应用变得简单、可维护,会使整个数据流更加清晰,是一个单一的从上而下的数据流,可以非常容易地精确地定位到需要改变哪个状态去对应 UI。在必须使用状态的时候,编写有状态组件并在组件内部去组合其它无状态组件。

      例 子  

    import React, {PureComponent} from 'react';
    
    class NavBar extends PureComponent {
        render(){
            return(
                <nav className="navbar navbar-expand-lg navbar-light bg-light">
                    <div className="container">
                        <div className="wrap">
                            <span className="title">NAVBAR</span>
                            <span className="badge badge-pill badge-primary ml-2 mr-2">
                                {this.props.total}
                            </span>
                            <button
                                onClick={this.props.onReset}
                                className="btn btn-outline-success my-2 my-sm-0 fr"
                                type="button"
                            >
                                Reset
                            </button>
                        </div>
                    </div>    
                </nav>
            );
        }
    }
    
    export default NavBar;
    有状态组件 navbar.jsx

      在上面例子的基础上,将有状态组件 navbar.jsx 改成无状态组件。  

      在无状态组件中没有 render 方法,只需要在 return 中返回一段 React 元素。不能使用 this 关键字去引用 props。

    import React from 'react';
    
    const NavBar = ( props ) => {
        return ( 
            <nav className="navbar navbar-expand-lg navbar-light bg-light">
                <div className="container">
                    <div className="wrap">
                        <span className="title">NAVBAR</span>
                        <span className="badge badge-pill badge-primary ml-2 mr-2">
                            {props.total}
                        </span>
                        <button
                            onClick={props.onReset}
                            className="btn btn-outline-success my-2 my-sm-0 fr"
                            type="button"
                        >
                            Reset
                        </button>
                    </div>
                </div>    
            </nav>
        );
    }
     
    export default NavBar;
    无状态组件 navbar.jsx

    还有另一种更简单的写法,将 props 的内容解构出来,然后直接调用传入的参数。

    import React from 'react';
    
    const NavBar = ( {total, onReset} ) => {
        return ( 
            <nav className="navbar navbar-expand-lg navbar-light bg-light">
                <div className="container">
                    <div className="wrap">
                        <span className="title">NAVBAR</span>
                        <span className="badge badge-pill badge-primary ml-2 mr-2">
                            {total}
                        </span>
                        <button
                            onClick={onReset}
                            className="btn btn-outline-success my-2 my-sm-0 fr"
                            type="button"
                        >
                            Reset
                        </button>
                    </div>
                </div>    
            </nav>
        );
    }
     
    export default NavBar;
    无状态组件 navbar.jsx

    页面表现同上

  • 相关阅读:
    拉格朗日乘子法
    EM算法
    最大似然估计
    理解先验概率 后验概率 似然函数
    似然函数理解
    markdown 语法规则
    bash101总结
    hmm和Veterbi算法(一)
    Kaldi 安装
    通俗的解释交叉熵与相对熵
  • 原文地址:https://www.cnblogs.com/xiaoxuStudy/p/13350935.html
Copyright © 2011-2022 走看看