学习 React 第一节课:基础

448 阅读7分钟

前言

文档目录:

  1. 梦开始的地方
  2. 如何创造一个组件
  3. 属性 props
  4. 状态 state
  5. 生命周期 lifecycle

此文档为入门文档,暂时或以后不要用到的知识都给省略了

平时基本不用的尽量省略

优化相关尽量省略

React 17 相关尽量省略


一、梦开始的地方

React 文件一般用 .jsx 后缀来命名,但是也可以用 .js,两者从结果来说并无区别。

同理,如果是引入了 Typescript,还可以用 .tsx 或者 .ts 来命名

特殊的文件名后缀只是为了方便 Webpack 以及 IDE 的识别,其并无特殊意义

入口文件(main)的编写

import React from "react"; // 每个 react 文件都必须引入 react 库,否则无法识别 react 语法
import ReactDOM from "react-dom";

// 声明(创建)一个简单的组件
function MyFirstComponent() {
  return <div>hello world!</div>;
}

// 将组件渲染到实际 DOM 节点上
ReactDOM.render(
  <MyFirstComponent></MyFirstComponent>,
  document.getElementById("root")
);

二、如何创造一个组件

有且只有两种方式来构造一个组件

  • 函数式(Function Component)
  • 类定义(Class Component)

基础用法

下面分别用来两种方式来构造(渲染结果)一模一样的组件

import React from "react";

function MyFuncComp(props) {
  console.log(props);
  return <div>hello world!</div>;
}

class MyClassComp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    return <div>hello world!</div>;
  }
}

演示两者的区别

从可以实现的效果来说,两者的主要区别:

  • Function Component
    1. 没有生命周期钩子
    2. 无 state
  • Class Component
    1. 有生命周期钩子
    2. 有 state

从 React 17 开始,Function Component 均可以通过不同的 Hooks 来实现 Class Component 可以实现的功能

目前为止,你只需要了解最基本的编写方法即可,暂且用上面所表述的那样来区别两者

有兴趣可以先去了解下,如何用 Hooks 来实现 React Class Component 写法

class MyClassComp extends React.Component {
  constructor(props) {
    super(props);

    // 在构造函数中初始化 state
    this.state = {
      num: 0,
    };
  }

  // 各种生命周期钩子
  componentWillMount() {}
  componentDidMount() {}
  // shouldComponentUpdate(){}

  // 声明响应点击的方法
  handleAddClick = () => {
    // Class Component 通过 setState 方法来改变 state
    this.setState({
      num: this.state.num + 1,
    });
  };
  render() {
    // 内部除了 props,还有 state
    console.log(this.props, this.state);
    return (
      <div>
        <p>点击次数:{this.state.num}</p>
        <p onClick={this.handleAddClick}>点击我增加次数</p>
      </div>
    );
  }
}

三、属性 props

父组件通过 props 传递属性(或方法)给子组件

function Child(props) {
  return <div>父组件传递的props:{props.test}</div>;
}
function Parent() {
  return <Child test="123"></Child>;
}

子节点 children

props 中有一个特殊的属性 children 用以存放父组件传递的子节点

并且 children 的值有三种可能:

  1. undefined 当前组件没有子节点
  2. object 当前组件有一个子节点
  3. array 当前组件有多个子节点

关于 react 中的一些关于操作节点的 api,这里就不展开了

React.ChildrenReact.cloneElement 等等

import React from "react";

function Child(props) {
  // const items=React.Children.only(props.children)   // 只能返回一个children(对象),若children不止一个,则会抛出异常
  // const items=props.children.map(item=>item);      // 当children不止一个时,props.children为数组;只有一个时,为对象,所以props.children.map方法并不适用任何情况
  // const items=React.Children.map(props.children,item=>item);    // 适用与任一情况
  const items = React.Children.toArray(props.children); // 等价于上面一行
  return (
    <div>
      <p>父组件传递的一般属性:{props.test}</p>
      <div>父组件子节点:{props.children}</div>
      <div>父组件子节点:{items}</div>
    </div>
  );
}
function Parent() {
  return (
    <Child test="123">
      <p>子节点1</p>
      <p>子节点2</p>
    </Child>
  );
}

默认 props

通过 defaultProps 静态属性来定义该组件的默认 props

// Function Component
function MyCompOne(props) {
  return <div>{props.myStr}</div>;
}
MyCompOne.defaultProps = {
  myStr: "hello world",
};

// Class Component
class MyCompOne extends React.Component {
  static defaultProps = {
    myStr: "hello world",
  };
  render() {
    return <div>{this.props.myStr}</div>;
  }
}

其他

相关还有其他知识点,这里就不展开了

  • props 的校验
  • 方法传递时 this 指向问题
  • 解构方式传递 props

四、状态 state

state 是一个组件的数据模型,是组件渲染时的数据依据,它与 props 共同来推动视图的渲染

改变 state

react 中通过 setState 来触发 state 的变化

有一下几点需要注意的:

  1. 不允许直接通过赋值语句来改变 state 的值
  2. 不是每次调用 setState 都会触发一次 render 的,state 所触发的更新是异步的
  3. 不要将“改变不需要触发重绘”的数据放在 state 中,这里会放到“性能优化”中详细介绍
class MyClassComp extends React.Component {
  constructor(props) {
    super(props);

    // 在构造函数中初始化 state
    this.state = {
      num: 0,
    };
  }

  // 声明响应点击的方法
  handleAddClick = () => {
    // Class Component 通过 setState 方法来改变 state
    this.setState({
      num: this.state.num + 1,
    });
  };

  render() {
    return (
      <div>
        <p>点击次数:{this.state.num}</p>
        <p onClick={this.handleAddClick}>点击我增加次数</p>
      </div>
    );
  }
}

五、生命周期 lifecycle

不同生命周期在组件的不同阶段被调用的顺序

这是 React 16 前的流程,后续也就只是针对 will 类型的钩子进行修改,目前开发过程中尽量不使用 will 之类的生命周期即可

Initialization
  1. setup props and state
Mounting
  1. componentWillMount
  2. render
  3. componentDidMount
Updation
  1. componentWillReceiveProps
  2. shouldComponentUpdate
  3. componentWillUpdate
  4. render
  5. getSnapshotBeforeUpdate
  6. componentDidUpdate
Unmounting
  1. shouldComponentUpdate
  2. componentWillUpdate
  3. render
  4. getSnapshotBeforeUpdate
  5. componentDidUpdate

componentDidMount

组件挂载到 DOM 后调用,且只会被调用一次

一般就是在这里做一些诸如”发请求获取详情“等逻辑

function timeout(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

class ChildComp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: false,
      name: "",
    };
  }

  async componentDidMount() {
    // 展示 loading 效果
    this.setState({
      loading: true,
    });
    await timeout(1000); // 用一个延时来模拟异步请求
    this.setState({
      loading: false,
      name: "张大炮",
    });
  }

  render() {
    const { loading, name } = this.state;
    if (loading) {
      return <div>加载中...</div>;
    } else {
      return <div>我的名字:{name}</div>;
    }
  }
}

shouldComponentUpdate

当组件的更新机制被触发时,判断组件是否需要重新渲染(rerender)

触发组件的更新机制有以下几种场景:

  1. 父组件 rerender
  2. 自身 state 发生改变

state/props 发生变化时肯定会进入 shouldComponentUpdate,但是反之不是

譬如,当父组件导致子组件更新时,即使子组件接收的 props 是否发生改变,子组件都会触发更新机制

class ChildComp extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      num: 0,
    };
  }

  handleAddClick = () => {
    this.setState({
      num: this.state.num + 1,
    });
  };

  // shouldComponentUpdate(nextProps, nextState) {
  //   // 每次 props 或者 state 的改变都会进入本方法
  //   return true; // 方法需要传递一个 boolean 的返回值,true 表示“要渲染”
  // }
  shouldComponentUpdate(nextProps, nextState) {
    // 判断前后的 props 或 state 是否发生了变化,来决定是否要重新渲染
    return (
      this.props.test !== nextProps.test || this.state.num !== nextState.num
    );
  }

  render() {
    return (
      <div>
        <p>父组件传递的属性:{this.props.test}</p>
        <p>点击次数:{this.state.num}</p>
        <p onClick={this.handleAddClick}>点击我增加次数</p>
      </div>
    );
  }
}

shouldComponentUpdate 提供了组件渲染的优化方式,可以不写,其默认处理方式就是“始终返回 true”

还有一种内置 shouldComponentUpdate 的 Class Component,为 React.PureComponent,目前可以不需要了解,这里就不展开了

Class Component 还有一个 this.forceUpdate() 的内置方法,用于强制触发重新渲染(不用经过 shouldComponentUpdate),这里就不展开了,自行了解

componentDidUpdate

在组件重新渲染完成之后调用,并且首次渲染不会调用

componentDidUpdate(prevProps, prevState, snapshot){
  if (this.props.userID !== prevProps.userID) {
    this.fetchData(this.props.userID);
  }
}

你可以在 componentDidUpdate 中调用 setState,但请注意,它必须像上面的例子那样被包装在一个条件中,否则有可能会导致无限循环。

不建议将父组件传递的 props 复制到子组件的 state 上,将 props 拷贝到 state 上会导致 bug

componentWillUnmout

在组件被销毁前会进入本生命周期

在这里一般需要进行以下操作:

  1. 清除掉你在本组件中使用的定时器 clearTimeout clearInterval
  2. 移除掉你在本组件中挂在的监听 removeEventListener
  3. 中止异步请求
class MyComp extends React.Component {
  myInterval; // 要用一个属性来接收使用到的定时器
  constructor(props) {
    super(props);
    this.state = {
      second: 0,
      size: "",
    };
  }
  componentDidMount() {
    // 绑定定时器
    this.myInterval = window.setInterval(
      () =>
        this.setState({
          second: this.state.second + 1,
        }),
      1000
    );

    // 绑定监听
    window.addEventListener("resize", this.handleWindowResize);

    // 发送异步请求
    // TODO
  }
  componentWillUnmount() {
    // 清除定时器
    window.clearInterval(this.myInterval);

    // 清除监听
    window.removeEventListener("resize", this.handleWindowResize);

    // 取消(未完成的)异步请求
    // TODO
  }
  handleWindowResize = () => {
    this.setState({
      size: `${window.innerWidth}*${window.innerHeight}`,
    });
  };
  render() {
    return (
      <div>
        <p>倒计时:{this.state.second}</p>
        <p>页面尺寸:{this.state.size}</p>
      </div>
    );
  }
}

其他

static getDerivedStateFromPropsgetSnapshotBeforeUpdate,应用面少,这里不展开了

在 react 17 中,以下几个生命周期将会被修改,并且必须加上 UNSAFE_ 才行:

  • UNSAFE_componentWillMount 初始化逻辑应该在 componentDidMount 上实现
  • UNSAFE_componentwillReceiveProps 原来功能建议在 componentDidUpdate 上实现
  • UNSAFE_componentWillUpdate 这个实在想不出来能在什么地方用

这些 UNSAFE 的生命周期在后续还会被考虑直接移除,所以新的项目能不使用就不要使用了

至于为什么这些 componentWill 之类的东西要考虑被移除,可以去了解 React Fiber