React 组件分类

  1. 根据组件的定义方式,可以分为:
    1. 函数组件 (Functional Component )
    2. 类组件 (Class Component)
  2. 根据组件内部是否有状态需要维护,可以分成:
    1. 无状态组件 (Stateless Component)
    2. 有状态组件 (Stateful Component)
  3. 根据组件的不同职责,可以分成:
    1. 展示型组件 (Presentational Component)
    2. 容器型组件 (Container Component)
  4. 这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:
    1. 函数组件、无状态组件、展示型组件主要 关注 UI 的展示;
    2. 类组件、有状态组件、容器型组件主要 关注数据 逻辑;

创建 React 组件

  1. 组件的名称首字母必须大写;

  2. React 中的哲学:数据属于谁,谁才有权力改动;

创建类组件

  1. 类组件的定义有如下要求:

    1. 类组件需要继承自 React.Component
    2. 类组件必须实现 render 函数
  2. ES6 之前,可以通过 create-react-class 模块来定义类组件,但是目前官网建议使用 ES6class 类定义;

  3. 使用 class 定义一个组件:

    1. constructor 是可选的,通常在 constructor 中初始化一些数据;
    2. this.state 中维护的是组件内部的数据;
    3. render() 方法是 class 组件中唯一必须实现的方法;当 render 被调用时,它会检查 this.propsthis.state 的变化并返回以下类型之一:
      1. React 元素:通常通过 JSX 创建;
      2. Portals:可以渲染子节点到不同的 DOM 子树中;
      3. 字符串数值类型:它们在 DOM 中会被渲染为文本节点;
      4. 布尔类型null:什么都不渲染;
    import React, { Component } from 'react';
    
    export default class App extends Component {
      constructor() {
        super();
    
        this.state = {
          
        }
      }
    
      render() {
        return <h2>Hello App</h2>
      }
    }
    

创建函数组件

  1. 函数组件是使用 function 来进行定义的函数,只是这个函数会返回和类组件中 render 函数返回一样的内容;

  2. 函数组件有自己的特点:

    1. 没有生命周期,也会被更新并挂载,但是没有生命周期函数;
    2. 没有 this (组件实例)
    3. 没有内部状态 (state)
    export default function App() {
      return (
        <div>Hello World</div>
      )
    }
    

组件状态

类组件中的状态

  1. 组件状态:组件可以自行维护的数据,组件状态仅在 类组件 中有效;

    1. 状态 (state),本质上是类组件的一个属性,是一个对象;
    2. 不能直接改变状态:因为 React 无法监控到状态发生了变化,必须使用 this.setState 改变状态,一旦调用了 this.setState,会导致当前组件重新渲染;
  2. 组件中的数据:

    1. props:该数据是由组件的使用者传递的数据,所有权不属于组件自身;
    2. state:该数组是由组件自身创建的,所有权属于组件自身,因此组件有权改变该数据;
  3. setState() 语法:

    1. 对象形式:当不需要基于当前状态进行更新,‌或者更新不需要依赖之前的 stateprops 时,‌应使用对象形式,‌这种形式直接传递一个新状态对象给 setState,‌它会与当前状态进行浅合并;
      this.setState({ counter: 10 }, () => {
        // 状态完成改变之后触发,该回调运行在 render 之后
      });
      
    2. 函数形式:当需要基于当前的状态来进行更新时,‌应使用函数形式;
      /**
       * prevState 代表当前状态,可以获取到之前的状态值;
       * 返回值会覆盖掉之前的状态
       * 该函数是异步执行的
       */
      this.setState((prevState) => ({ counter: prevState.counter + 1 }));
      
  4. 深入认识 setState

    1. setState 它对状态的改变,可能是 同步 的、异步 的;
    2. 最佳实践:
      1. 把所有的 setState 当作是异步的;
      2. 永远不要信任 setState 调用之后的状态;
      3. 如果要使用改变之后的状态,需要使用回调函数 (setState 对象形式的第二个参数)
      4. 如果新的状态要根据之前的状态进行运算,使用函数的方式改变状态 (setState 的函数形式)
    3. React 会对异步的 setState 进行优化,将多次 setState 进行合并 (将多次状态改变完成后,再统一对 state 进行改变,然后触发 render,react 源码中实现:在 html 中事件函数中的状态会异步更新,其他地方的状态会同步更新)
  5. 示例:

    import React, { PureComponent } from "react";
    
    export default class Counter extends PureComponent {
      constructor(props) {
        super(props);
    
        this.state = {
          counter: 0,
        };
      }
    
      render() {
        return (
          <div>
            <h2>当前计数: {this.state.counter}</h2>
            <button onClick={(e) => this.increment()}>+1</button>
            <button onClick={(e) => this.decrement()}>-1</button>
          </div>
        );
      }
    
      increment() {
        // this.setState({ counter: this.state.counter + 1 });
        this.setState((curr)=> ({ counter: curr.counter + 1 }));
      }
    
      decrement() {
        this.setState({ counter: this.state.counter - 1 });
      }
    }
    

函数组件中的状态

  1. 使用 React Hook 中的 useState 可以在函数组件中使用状态;

  2. 示例:

    // 函数式组件 + hook 实现
    import React, { useState } from "react";
    
    export default function Counter() {
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <h2>当前计数: {count}</h2>
          <button onClick={(e) => setCount(count + 1)}>+1</button>
          <button onClick={(e) => setCount(count - 1)}>-1</button>
        </div>
      );
    }
    

State Hook

语法

const [state, setState] = useState(initialState);

  1. 在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同;
  2. setState 函数用于更新 state,它接收一个新的 state 值并将组件的一次重新渲染加入队列;

数据更新(基本)

  1. 问题:点击按钮后连续调用 3setCount(count + 1),最后界面上 count 的值并没有 +3,仍然是 +1

    import React, { useState } from "react";
    
    export default function () {
      const [count, setCount] = useState(0);
    
      function handleOnClick() {
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
      }
    
      return (
        <div>
          <div>count: {count}</div>
          <button onClick={handleOnClick}>+1</button>
        </div>
      );
    }
    
  2. 解决:函数式更新 (新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState

    setCount(count => count + 1);
    setCount(count => count + 1);
    setCount(count => count + 1);
    

数据更新(引用)

  1. 问题:当 useState 的值为对象时,可能会存在视图不更新的情况;

    import React, { useState } from "react";
    
    export default function () {
      const [list, setList] = useState([0, 1, 2]);
      const [useInfo, setUserInfo] = useState({
        name: "张三",
        age: 18,
      });
    
      function handleOnClick() {
        list.push(4);
        list.push(4);
        setList(list);
    
        useInfo.name = "李四";
        useInfo.age = 20;
        setUserInfo(useInfo);
      }
    
      return (
        <div>
          <p>姓名:{useInfo.name}</p>
          <p>年龄:{useInfo.age}</p>
          <p>ist.length: {list.length}</p>
          <button onClick={handleOnClick}>修改</button>
        </div>
      );
    }
    
  2. 问题原因:React 中默认是浅监听,当 state 的值为对象时,栈中存的是对象的引用 (地址)setState 改变的是堆中的数据,栈中的地址还是原地址,React 浅监听到地址没变,故会认为 State 并未改变,所以没有重渲染页面;

  3. 解决方案:只要改变了原对象的地址即可,可通过 克隆、拓展运算符 实现;

    function handleOnClick() {
      setList([...list, 4, 4]);
    
      useInfo.name = "李四";
      useInfo.age = 20;
      setUserInfo({ ...useInfo });
    }
    

获取最新的值

setSate 后拿到最新的值 (由于 setSate 后并不会立即更新,React 会在某个时候将多个 setSate 进行合并后再更新)

  1. 使用 useRef,但是数据的更新不会引起视图的更新;

    import React, { useState, useRef, useEffect } from "react";
    
    export default function () {
      const [count, setCount] = useState(0);
      const countRef = useRef(0);
    
      function handleOnClick() {
        countRef.current += 1;
        setCount(count + 1);
    
        console.log("正常打印", count); // 获取的还是之前的值
        console.log("countRef", countRef.current); // 获取的是更新后的值
      }
    
      return (
        <div>
          <div>count: {count}</div>
          <button onClick={handleOnClick}>+1</button>
        </div>
      );
    }
    
  2. 使用 useEffect,这种方式在很多场景下也不适用,每次更新都会执行 useEffect 中的内容,往往需求并不是如此;

    import React, { useState, useRef, useEffect } from "react";
    
    export default function () {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        console.log("useEffect", count); // 获取的是更新后的值
      }, [count]);
    
      function handleOnClick() {
        setCount(count + 1);
    
        console.log("正常打印", count); // 获取的还是之前的值
      }
    
      return (
        <div>
          <div>count: {count}</div>
          <button onClick={handleOnClick}>+1</button>
        </div>
      );
    }
    
  3. 使用函数式更新;

    import React, { useState, useRef, useEffect } from "react";
    
    export default function () {
      const [count, setCount] = useState(0);
      const countRef = useRef(0);
    
      function handleOnClick() {
        setCount(count + 1);
    
        console.log("正常打印", count); // 获取的还是之前的值
        setCount((count) => {
          console.log("函数式更新获取最新值", count); // 获取的是更新后的值
          return count;
        });
      }
    
      return (
        <div>
          <div>count: {count}</div>
          <button onClick={handleOnClick}>+1</button>
        </div>
      );
    }
    
  4. 使用 ahooksuseGetState (原理:使用 useRefuseState 的值存起来)

    import React, { useState, useRef, useCallback } from "react";
    
    const useGetState = (initVal) => {
      const [state, setState] = useState(initVal);
      const ref = useRef(initVal);
      const setStateCopy = (newVal) => {
        ref.current = newVal;
        setState(newVal);
      };
      const getState = () => ref.current;
      return [state, setStateCopy, getState];
    };
    
    export default function () {
      const [count, setCount, getCount] = useGetState(0);
    
      function handleOnClick() {
        setCount(count + 1);
    
        console.log("正常打印", count); // 获取的还是之前的值
        console.log("useGetState", getCount()); // 获取的是更新后的值
      }
    
      return (
        <div>
          <div>count: {count}</div>
          <button onClick={handleOnClick}>+1</button>
        </div>
      );
    }
    

组件状态案例:弹性小球

JavaScript
JavaScript
JavaScript
JavaScript
css
// index.js
import React from "react";
import ReactDOM from "react-dom";
import BallList from "./components/BallList";

ReactDOM.render(<BallList />, document.getElementById("root"));
// BallList.js
import React, { Component } from "react";
import Ball from "./Ball";
import { getRandom } from "../util";

/**
 * 每隔一段时间,自动产生一个小球,各种数据随机
*/
export default class BallList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      ballInfoes: [],
    };
    const timer = setInterval(() => {
      var info = {
        left: getRandom(0, document.documentElement.clientWidth - 100),
        top: getRandom(0, document.documentElement.clientHeight - 100),
        xSpeed: getRandom(50, 500),
        ySpeed: getRandom(50, 500),
        bg: `rgb(${getRandom(0, 255)}, ${getRandom(0, 255)}, ${getRandom( 0, 255 )})`,
      };
      this.setState({
        ballInfoes: [...this.state.ballInfoes, info],
      });

      if (this.state.ballInfoes.length === 10)  clearInterval(timer);
    }, 1000);
  }

  render() {
    const balls = this.state.ballInfoes.map((item, i) => (
      <Ball key={i} {...item} />
    ));
    return <>{balls}</>;
  }
}
// Ball.js
import React, { Component } from "react";
import "./Ball.css";

/**
 * 一个能够自动移动的小球
*/
export default class Ball extends Component {
  constructor(props) {
    super(props);
    // 属性中需要分别传递横纵坐标上的速度,每秒移动的像素值 props.xSpeed,  props.ySpeed
    // 需要传递背景颜色,如果没有传递,则使用红色
    this.state = {
      left: props.left || 0, //横坐标
      top: props.top || 0, //纵坐标
      xSpeed: props.xSpeed,
      ySpeed: props.ySpeed,
    };
    const duration = 16; // 间隔的毫秒数

    setInterval(() => {
      const xDis = (this.state.xSpeed * duration) / 1000;
      const yDis = (this.state.ySpeed * duration) / 1000;

      // 根据速度,改变 left、top 值
      let newLeft = this.state.left + xDis;
      let newTop = this.state.top + yDis;

      if (newLeft <= 0) {
        newLeft = 0;
        // 横坐标反向
        this.setState({ xSpeed: -this.state.xSpeed });
      } else if (newLeft >= document.documentElement.clientWidth - 100) {
        newLeft = document.documentElement.clientWidth - 100;
        // 横坐标反向
        this.setState({ xSpeed: -this.state.xSpeed });
      }

      if (newTop <= 0) {
        newTop = 0;
        // 纵坐标反向
        this.setState({ ySpeed: -this.state.ySpeed });
      } else if (newTop >= document.documentElement.clientHeight - 100) {
        newTop = document.documentElement.clientHeight - 100;
        // 纵坐标反向
        this.setState({ ySpeed: -this.state.ySpeed });
      }

      this.setState({ left: newLeft, top: newTop });
    }, duration);
  }

  render() {
    return (
      <div className="ball" style={{ left: this.state.left, top: this.state.top, background: this.props.bg || "#f40" }} ></div>
    );
  }
}
// util.js
export function getRandom(min, max) {
    return Math.floor(Math.random() * (max + 1 - min) + min);
}
.ball{
    width: 100px;
    height: 100px;
    border-radius: 50%;
    position: fixed;
}
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. React 组件分类
  2. 2. 创建 React 组件
    1. 2.1. 创建类组件
    2. 2.2. 创建函数组件
  3. 3. 组件状态
    1. 3.1. 类组件中的状态
    2. 3.2. 函数组件中的状态
  4. 4. State Hook
    1. 4.1. 语法
    2. 4.2. 数据更新(基本)
    3. 4.3. 数据更新(引用)
    4. 4.4. 获取最新的值
  5. 5. 组件状态案例:弹性小球