20230519----重返学习-React类组件的特点及受控组件与非受控组件-TodoList使用Antd框架及React组件绑定事件

day-073-seventy-three-20230519-React类组件的特点及受控组件与非受控组件-TodoList使用Antd框架及React组件绑定事件

React类组件的特点及受控组件与非受控组件

类组件的特点

  1. 类组件动态组件,它拥有属性props上下文状态state钩子函数refs等内容。

    • 在组件内部,单纯地改动组件实例对象的状态,并不能像vue一样改变视图。

      import { Component } from "react";
      class DemoTwo extends Component {
        /* 属性规则校验 */
        static defaultProps = {
          x: 0,
          y: false,
        };
        /* 初始化状态 */
        state = {
          num: this.props.x, //10
          count: 0,
        };
        render() {
          console.log("render");
          let { title } = this.props,
            { num, count } = this.state;
          return (
            <div className="box">
              {title} && {num} && {count}
              <br />
              <button
                onClick={() => {
                  this.state.num++; //这样操作,仅仅是立即修改了状态值,但是视图不会更新
                  console.log(this.state.num);
                }}
              >
                修改NUM
              </button>
              <br />
              <button>修改COUNT</button>
            </div>
          );
        }
      }
      export default DemoTwo;
      
      • 这样操作,仅仅是立即修改了状态值,但是视图不会更新。
    • 在组件内部,可以基于修改状态强制更新父组件重新调用此组件等方式,让组件更新。

      • 类组件的更新,不是重新创建一个新的实例,而是把实例上的render()方法重新执行,用最新的数据,渲染出新的VirtualDOM虚拟DOM,再经过DOM-diff,进行差异化更新

        • 渲染–狭义来说一般指的是第一次VirtualDOM虚拟DOM渲染进页面。
        • 更新–狭义来说一般指的是非第一次重新调用render()方法重新更新页面。
      • 通过setState()方法修改状态,可以控制组件更新页面。

        <button
          onClick={() => {
            this.setState({ num: num + 1 }, () => {
              console.log(`setState触发的回调函数callback`,this.state);
            });
          }}
        >
          修改num
        </button>
        
        import { Component } from "react";
        import PT from "prop-types";
        
        class DemoTwo extends Component {
          /* 属性规则校验 */
          static defaultProps = {
            x: 0,
            y: false,
          };
          static propTypes = {
            title: PT.string.isRequired,
            x: PT.oneOfType([PT.number, PT.string]),
            y: PT.bool,
          };
          /* 初始化状态 */
          state = {
            num: this.props.x, //10;
            count: 0, //
          };
        
          render() {
            let { title } = this.props;
            let { num, count } = this.state;
            console.log(this);
            return (
              <div className="box">
                title:{title} -- num:{num} --count:{count}
                <br />
                <button
                  onClick={() => {
                    /* this.state.num++;//这样操作,仅仅是立即修改了状态值,但是视图不会更新。
                    console.log(this.state.num); */
        
                    // 只有基于特定的方法setState修改状态,才可以保证,状态更新的同时,视图也会立即更新。
                  }}
                >
                  修改num
                </button>
                <br />
                <button>修改count</button>
              </div>
            );
          }
        }
        export default DemoTwo;
        
        1. 触发安全的shouldComponentUpdate()钩子函数。

          • shouldComponentUpdate()表示是否允许更新。

            • 方法返回true,说明允许更新,则进行下一步的操作。
            • 如果返回false,则是禁止其更新,那么所有的操作到此结束。
              • 但是依旧会把setState(dataObject,callback)中的callback()执行。
            • 如果不写该shouldComponentUpdate()函数,默认返回true
          • 在执行这个钩子函数的时候,状态值还没有改变。

            • 或者说还没有修改this实例上的状态this实例上的属性

              shouldComponentUpdate(nextProps, nextState) {
                // nextProps & nextState:即将要修改的最新的属性和状态。
                // this.props & this.state :原有的属性和状态。
                //return true
                return false //方法返回true,说明允许更新,则进行下一步的操作。如果返回false,则是禁止其更新,那么所有的操作到此结束。
              }
              
            • shouldComponentUpdate(nextProps, nextState)中:

              • nextProps:即将要修改的最新的属性;
              • nextState:即将要修改的最新的状态;
              • this.props:原有的属性;
              • this.state:原有的状态;
          • 真实项目中,一般在此钩子函数中进行优化!

            • 比如说:
              • 改了一些状态,但不想让它更新,就返回false。
                • 因为如果返回true,那么就React就会产生VirtualDOM虚拟DOM,之后进行比对,比较消耗性能。
        2. 触发不安全的componentWillUpdate()钩子函数。

          • 触发时期:更新之前。

          • 此钩子函数是不安全的,不建议使用。

            • 可以忽略它,改用 shouldComponentUpdate()
          • 如果不写该函数,默认返回true。

          • 执行此钩子函数的时候,实例上的状态值还是没有更改的。

            UNSAFE_componentWillUpdate(nextProps, nextState) {
              console.log(
                `componentWillUpdate(nextProps, nextState)`,
                nextState,
                this.state
              );
              return true;
            }
            
            componentWillUpdate(nextProps, nextState) {
              // nextProps & nextState:即将要修改的最新的属性和状态。
              // this.props & this.state :原有的属性和状态。
            
              // ...
            
              return true //方法返回true,说明允许更新,则进行下一步的操作。
              //return false//如果返回false,则是禁止其更新,那么所有的操作到此结束。
            }
            
        3. 内部开始把实例上的状态state实例上的属性props进行更改。

        4. 触发render()钩子函数。

          • 组件开始更新。
            • 按照最新的数据,把所有视图重新生成新的VirtualDOM虚拟DOM,然后和之前的虚拟DOM进行对比,最后把差异的部分进行更新渲染。
        5. 触发安全的componentDidUpdate()钩子函数。

          • 组件更新完毕。

            componentDidUpdate(){
              // ...
            }
            
        6. 如果执行setState(修改的状态,callback)时传递了callback函数,则在DOM更新完毕后,会把callback函数触发执行!

          • 如果是不论那个状态改变,在改变后都要执行的事件,我们写在componentDidUpdate()中!
          • 但是如果是指定的状态改变,才会去做的事件,我们一般写在this.setState(修改的状态,callback)对应的callback函数中!
        • Vue可以通过get()set()劫持,在修改数据时,通知视图执行。其实也类似于React的this.setState()

          import { Component } from "react";
          class DemoTwo extends Component {
            /* 属性规则校验 */
            static defaultProps = {
              x: 0,
              y: false,
            };
            /* 初始化状态 */
            state = {
              num: this.props.x, //10
              count: 0,
            };
            render() {
              console.log("render");
              let { title } = this.props,
                { num, count } = this.state;
              return (
                <div className="box">
                  {title} && {num} && {count}
                  <br />
                  <button
                    onClick={() => {
                      // 只有基于特点的方法 setState 修改状态,才可以保证,状态更新的同时,视图也会更新
                      // Component.prototype.setState = function (partialState, callback) {...}
                      // setState是支持部分状态更改的
                      this.setState(
                        {
                          num: num + 1,
                        },
                        () => {
                          console.log("callback");
                        }
                      );
                    }}
                  >
                    修改NUM
                  </button>
                  <br />
                  <button>修改COUNT</button>
                </div>
              );
            }
          }
          export default DemoTwo;
          
      • 通过forceUpdate()让视图强制更新。

        Component.prototype.forceUpdate = function (callback) {...}
        
        <button
          onClick={() => {
            // 强制更新
            this.state.num++
            this.forceUpdate(()=>{
                console.log(`forceUpdate触发的回调函数callback`,this.state);
            })
          }}
        >
          修改num
        </button>
        
        1. 触发不安全的componentWillUpdate()钩子函数。
        2. 触发render()钩子函数。
        3. 触发安全的componentDidUpdate()钩子函数。
        4. this.forceUpdate()中传递的callback函数执行。
          • 真实项目中,一般很少使用forceUpdate()
            • 因为其太暴力了,会忽略在shouldComponentUpdat()中做的优化项!
        • 此方法通知视图更新,会直接跳过shouldComponentUpdate()钩子函数。

          import { Component } from "react";
          class DemoTwo extends Component {
            /* 属性规则校验 */
            static defaultProps = {
              x: 0,
              y: false,
            };
            /* 初始化状态 */
            state = {
              num: this.props.x, //10
              count: 0,
            };
            render() {
              console.log("render");
              let { title } = this.props,
                { num, count } = this.state;
              return (
                <div className="box">
                  {title} && {num} && {count}
                  <br />
                  <button
                    onClick={() => {
                      // 强制更新
                      this.state.num++;
                      this.forceUpdate(() => {
                        console.log("forceUpdate callback");
                      });
                    }}
                  >
                    修改NUM
                  </button>
                  <br />
                  <button>修改COUNT</button>
                </div>
              );
            }
          }
          export default DemoTwo;
          
      • 当父组件重新调用子组件。

        • 或者说父组件更新,那么它所涉及的子组件也会跟着更新
        • 重新调用子组件就可以传递最新的属性值进来!
          1. 触发不安全componentWillReceiveProps(nextProps)钩子函数。
            • 接收新的属性值props之前
            • 此时this.props中的值还没变,nextProps存储的是最新要修改的属性props
          2. 触发安全的shouldComponentUpdate()钩子函数。
          3. 触发不安全的componentWillUpdate()钩子函数。
          4. 内部开始把实例上的状态state实例上的属性props进行更改。
          5. 触发render()钩子函数。
          6. 触发安全的componentDidUpdate()钩子函数。
        • 也就是说平常当前组件更新,那么当前组件的所有子组件也会跟着更新
    import { Component } from "react";
    import PT from "prop-types";
    
    class DemoTwo extends Component {
      //   属性规则校验
      static defaultProps = {
        x: 0,
        y: false,
      };
      static propTypes = {
        title: PT.string.isRequired,
        x: PT.oneOfType([PT.number, PT.string]),
        y: PT.bool,
      };
    
      //   初始化状态
      state = {
        num: this.props.x, //10
        count: 0,
      };
    
      render() {
        console.log("render");
        let { title } = this.props,
          { num, count } = this.state;
        return (
          <div className="box">
            {title} && {num} && {count}
            <br />
            <button
              onClick={() => {
                // 只有基于特点的方法 setState 修改状态,才可以保证,状态更新的同时,视图也会更新
                // Component.prototype.setState = function (partialState, callback) {...}
                // setState是支持部分状态更改的
                this.setState(
                  {
                    num: num + 1,
                  },
                  () => {
                    console.log("this.setState()回调函数callback();");
                  }
                );
    
                // // 强制更新
                // this.state.num++;
                // this.forceUpdate(() => {
                //   console.log("forceUpdate callback");
                // });
              }}
            >
              修改NUM
            </button>
            <br />
            <button>修改COUNT</button>
          </div>
        );
      }
    
      // 父组件props更新。
      componentWillReceiveProps(nextProps) {
        console.log(`componentWillReceiveProps()父组件props更新。`, nextProps);
        return true;
      }
    
      // 子组件setState(),视图应该更新时。
      shouldComponentUpdate(nextProps, nextState) {
        console.log("shouldComponentUpdate视图应该更新时。", nextState, this.state);
        return true;
      }
    
      // 视图将要更新。
      UNSAFE_componentWillUpdate() {
        console.log("componentWillUpdate视图将要更新。", this.state);
        return true;
      }
    
      // 视图更新后。
      componentDidUpdate() {
        console.log("componentDidUpdate视图更新后。");
      }
    }
    export default DemoTwo;
    
  2. 组件总有一天会被销毁卸载

    • 例如:
      • 每次路由跳转,默认都是上一个组件卸载,新组件开始渲染。
        • 新组件第一次渲染逻辑
      • 当前组件的父组件销毁
    1. 触发componentWillUnmount()钩子函数。
      • 触发时期:组件卸载之前
      • 真实项目中,可以在卸载之前做的一些事情:
        • 消除手动设置的定时器、监听器。
        • 信息草稿箱。
          • 如保存一些数据,下一次重新渲染时,复用这些数据。
        • 弹出弹框提示用户当前组件中的数据可能会被丢失。
    import { Component } from "react";
    class DemoTwo extends Component {
      render() {
        return (
          <div className="box">
            <br />
            <button>修改COUNT</button>
          </div>
        );
      }
    
      // 组件将要销毁前。
      componentWillUnmount() {
        console.log("componentWillUnmount()组件将要销毁前。");
        return true;
      }
    }
    export default DemoTwo;
    

受控组件与非受控组件

  • 受控组件:由数据/状态来管控视图的渲染。

    // 受控组件:由数据/状态来管控视图的渲染
    class DemoThree extends Component {
      state = {
        show: true,
      };
      render() {
        let { show } = this.state;
        return (
          <div className="box">
            <button
              onClick={() => {
                this.setState({
                  show: !show,
                });
              }}
            >
              开关
            </button>
            <br />
            <div
              className="detail"
              style={{
                display: show ? "block" : "none",
              }}
            >
              我是一个详细信息
            </div>
          </div>
        );
      }
    }
    export default DemoThree;
    
    • 组件内部使用数据与状态来管控视图的渲染。

      // 受控组件-由数据与状态来管控视图的渲染。
      class RefsDemo extends Component {
        state = {
          show: true,
        };
        render() {
          let { show } = this.state;
          return (
            <div className="box">
              <button
                onClick={() => {
                  this.setState({ show: !show });
                }}
              >
                toggle
              </button>
              <br />
              <div
                className="detail"
                style={{
                  display: show ? `block` : `none`,
                }}
              >
                this a detail详细信息
              </div>
            </div>
          );
        }
      }
      
  • 非受控组件

    • 不受状态管控,想要修改视图就需要直接操作DOM–在React中是不推荐的。

      // 非受控组件--不受状态管控,直接操作DOM--在React中是不推荐的。
      // 我们需要获取DOM元素。
      // 契机:只有等待组件第一次渲染完毕后,我们才可以获取到真实DOM。
      class RefsDemo extends Component {
        state = {
          show: true,
        };
        render() {
          return (
            <div className="box">
              <button>toggle</button>
              <br />
              <div className="detail">this a detail详细信息</div>
            </div>
          );
        }
      }
      
    • 由于组件视图中不受状态管控,想要修改视图就只能直接操作DOM。

    • 假设我们需要获取DOM元素。

      • 契机:只有等待组件第一次渲染完毕后,我们才可以获取到真实DOM。
        • 契机例子:

          1. 在onClick等事件中。
          2. 在钩子函数中。
        • 可以基于传统获取DOM的方式如通过id来操作。

          • 这个方法在React中理论上是可以的,但实际操作起来,不建议使用。并且实际上很少有人使用。
        • 在React/Vue框架中,都有提供好的获取DOM元素的方式 --> ref="ref名";

          1. 很旧的方案:给ref属性设置为一个字符串。

            • <div ref="AAA">...</div>
            • this.refs.AAA -> DOM元素
            render() {
              return (
                <div className="box">
                  <button
                    onClick={() => {
                      console.log(this.refs, this.refs.AAA);
                    }}
                  >
                    toggle
                  </button>
                  <br />
                  <div className="detail" ref="AAA">
                    this a detail详细信息
                  </div>
                </div>
              );
            }
            
            • 但是这种方式官方已经不推荐了,在React.StrictMode模式下,控制台会报错Warning: A string ref
          2. 给ref设置的属性值是一个函数。

            • 比较推荐的方法。
              • <div ref={x => this.AAA = x}>;
              • this.AAA -> DOM元素;
            class RefsDemo extends Component {
              state = {
                show: true,
              };
              render() {
                return (
                  <div className="box">
                    <button
                      onClick={() => {
                        console.log(this.AAA);
                      }}
                    >
                      toggle
                    </button>
                    <br />
                    <div className="detail" ref={(x) => (this.AAA = x)}>
                      this a detail详细信息
                    </div>
                  </div>
                );
              }
            }
            
          3. 基于官方推荐的React.createRef() (类组件函数组件)与React.useRef() (函数组件)方法去处理。

            1. 基于React.createRef()React.useRef()创建一个ref对象{current:null} ;
            2. 元素的ref属性值,等于创建的ref对象
            3. 最后基于ref对象.current属性,获取真实的DOM
            import { createRef } from "react";
            class RefsDemo extends Component {
              state = {
                show: true,
              };
              AAA = createRef(); //this.AAA = {current:null}
              render() {
                return (
                  <div className="box">
                    <button
                      onClick={() => {
                        console.log(this.AAA.current);
                      }}
                    >
                      toggle
                    </button>
                    <br />
                    <div className="detail" ref={this.AAA}>
                      this a detail详细信息
                    </div>
                  </div>
                );
              }
            }
            
             ```jsx
             class DemoThree extends Component {
               AAA = createRef(); // this.AAA = { current: null }
               render() {
                 return (
                   <div className="box">
                     <button
                       onClick={() => {
                         console.log(this.AAA.current);
                       }}
                     >
                       开关
                     </button>
                     <br />
                     <div className="detail" ref={this.AAA}>
                       我是一个详细信息
                     </div>
                   </div>
                 );
               }
             }
             export default DemoThree;
             ```
            
        • ref除了可以获取视图中的真实DOM元素,还可以获取子组件的实例。

          • ref绑定一个元素标签,目的是获取这个真实的DOM。

          • ref绑定一个组件,目的:

            • 一般是获取组件的实例
            • 或者是获取组件内部的某些元素
            • 再或者是获取组件暴露出来的属性和方法
          • 给类组件绑定ref,最后获取的是类型为类组件的子组件实例对象

            class DetailBox extends Component {
              state = {x:100}
              render() {
                return <div className="detail">this a detail详细信息 --- {this.state.x}</div>;
              }
            }
            class RefsDemo extends Component {
              render() {
                return (
                  <div className="box">
                    <button
                      onClick={() => {
                        console.log(this.AAA);
                      }}
                    >
                      toggle
                    </button>
                    <br />
                    <DetailBox ref={(x) => (this.AAA = x)}>this a detail详细信息</DetailBox>
                  </div>
                );
              }
            }
            
            • 子组件的实例都获取到了后,那么在子组件的父组件当前组件中再想获取子组件中的什么信息,直接基于该子组件实例处理即可。

              import React, { Component, createRef } from "react";
              class DetailBox extends Component {
                state = { x: 100 };
                detailBox = createRef();
                render() {
                  return (
                    <div className="detail" ref={this.detailBox}>
                      我是一个详细信息 {this.state.x}
                    </div>
                  );
                }
              }
              class DemoThree extends Component {
                render() {
                  return (
                    <div className="box">
                      <button
                        onClick={() => {
                          console.log(this.AAA);
                        }}
                      >
                        开关
                      </button>
                      <DetailBox ref={(x) => (this.AAA = x)} />
                    </div>
                  );
                }
              }
              export default DemoThree;
              
              • 所有在子组件实例上挂载的东西,我们都可以基于this.AAA.xxx获取。
                • 如果需要获取子组件内部的元素:

                  1. 在子组件中,基于ref把对应的元素,挂载到子组件的实例上。
                  2. 这样父组件,就可以直接基于子组件实例去获取。
                  import { createRef } from "react";
                  class DetailBox extends Component {
                    state = { x: 100 };
                    detailBox = createRef();
                    render() {
                      return (
                        <div className="detail" ref={this.detailBox}>
                          this a detail详细信息 --- {this.state.x}
                        </div>
                      );
                    }
                  }
                  class RefsDemo extends Component {
                    render() {
                      return (
                        <div className="box">
                          <button
                            onClick={() => {
                              console.log(this.AAA);
                              console.log(this.AAA.state.x);
                              console.log(this.AAA.detailBox);
                            }}
                          >
                            toggle
                          </button>
                          <br />
                          <DetailBox ref={(x) => (this.AAA = x)}></DetailBox>
                        </div>
                      );
                    }
                  }
                  
          • 如果是给函数组件,绑定ref,则直接报错Warning: Function components cannot be given refs.

            function DetailBox() {
              return <div className="detail">我是一个详细信息</div>;
            }
            class RefsDemo extends Component {
              render() {
                return (
                  <div className="box">
                    <DetailBox ref={(x) => (this.AAA = x)}></DetailBox>
                  </div>
                );
              }
            }
            
            • 因为直接绑定是不被允许的。
              • 但是我们是有方法去解决的!

                import React, { Component, createRef } from "react";
                function DetailBox() {
                  return <div className="detail">我是一个详细信息</div>;
                }
                class DemoThree extends Component {
                  render() {
                    return (
                      <div className="box">
                        <button
                          onClick={() => {
                            console.log(this.AAA);
                          }}
                        >
                          开关
                        </button>
                        <DetailBox ref={(x) => (this.AAA = x)} />
                      </div>
                    );
                  }
                }
                export default DemoThree;
                

TodoList使用Antd框架及React组件绑定事件

  • React框架体系的UI组件库。
    • Atnd。
    • AntdMobile。

Antd框架

  • 使用Antd这个UI组件库(v5.0及之后)。
    • ant.design中文官网

    • 自动实现了按需导入。

      • 但是v5之前的版本,自动按需导入可能不生效,需要我们手动的处理一下。
        1. 安装babel-plugin-import这个配置。

          yarn add babel-plugin-import
          
        2. 配置/config/webpack.config.js/package.json其中一个关于babel-plugin-import这个babel插件中的配置。

        • /package.json

          {
              "babel": {
                "presets": [...],
                "plugins": [
                  [
                    "import",
                    {
                      "libraryName": "antd",
                      "style": true
                    }
                  ]
                ]
              }
          }
          
    • 我们在使用之前,需要进行国际化处理。

      • ant.design-安装及上手
        1. 默认组件库中的方案都是英文的,我们需要让其变为中文。

          1. 从antd引入ConfigProvider这个组件和中文语言包。

            import { ConfigProvider } from 'antd'
            import zhCN from 'antd/locale/zh_CN'
            
          2. 把需要转为中文的包含antd组件的组件都包裹在设置了locale这个props为中文语言包zhCN的ConfigProvider组件中。

            <ConfigProvider locale={zhCN}>
              //其它组件...
            </ConfigProvider>
            
                import { ConfigProvider } from 'antd'
                import zhCN from 'antd/locale/zh_CN'
                root.render(
                  <ConfigProvider locale={zhCN}>
                    //...
                  </ConfigProvider>
                )
            
          import React from "react";
          import ReactDOM from "react-dom/client";
          
          // antd的汉化。
          import { ConfigProvider } from 'antd';
          import zhCN from 'antd/locale/zh_CN';
          
          const root = ReactDOM.createRoot(document.getElementById("root"));
          root.render(
            <ConfigProvider locale={zhCN}>
              //其它组件。需要在<ConfigProvider>这个包起来。
            </ConfigProvider>
          );
          
        2. 但日期类组件,其内部的方案还没有汉化。

          • V5版本开始,日期类组件,其内部日期的处理,采用的是day.js,原来是moment.js插件。

            import dayjs from "dayjs";
            import "dayjs/locale/zh-cn";
            dayjs.locale("zh-cn");
            

React组件绑定事件

  • 直接调用定义在原型上的方法-不行。
  • 用bind绑定this之后执行放到原型上的方法。
  • 直接调用定义在实例上的箭头函数方法。
    • 这个是推荐的。
用bind实现预先传值
  • 基于bind实现预先传值。
    • 对于箭头函数,this是谁不重要。但可以用bind传递一些默认参数。

进阶参考

  1. ant.design中文官网
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值