react hooks之useState

·  阅读 375
react hooks之useState

前言

reacthooksreact 16.8引入的一个新特性, 旨在让开发者可以在不使用class的情况下使用state和其他的react特性(例如生命周期)

本篇文章主要聊聊useState具体使用方法, 首先需要注意的是:

  1. react hooks只能写在react function component(函数式组件)中

如果不了解什么是function component, 推荐阅读官方文档中关于函数式组件的定义: Function and Class Components

  1. 应该将所有的hooks都写到function component的最上方, 否则可能会导致一些问题, 比如这个问题: react hooks报错: Rendered fewer hooks/Rendered more hooks

概览

useState能够让我们在不使用class的情况下使用state

语法

在正文开始之前, 先来看看它的语法:

const [ currentValue, setValue ] = useState(initialValue);
复制代码

useState接收一个初始值作为参数, 返回一个数组

数组的第一个元素是当前值(初始化的时候是初始值, 而后每次更新了这个值则是更新之后的值)

第二个元素是一个用来修改这个值的函数, 这个函数接收一个新的值, 调用之后会更新定义时候的currentValue为新的值

这里也可以使用[0][1]来访问它们, 但那样的话稍显不妥, 因为它们有着特定的含义, 所以这里使用数组解构的语法来访问它们, 这也是官方文档推荐的一个写法

不了解数组解构语法的同学, 推荐阅读阮一峰老师的: ES6 入门教程 - ECMAScript 6入门-数组的解构赋值

接下来让我们动手写一写, 从我们熟悉的class形式的组件开始, 然后再试试函数式组件, 对比着来了解

class形式的组件

class中, 我们会这样写:

import React, { PureComponent } from 'react';

class BruceLee extends PureComponent {

  state = {
    name: '李小龙',
    career: '武术家, 演员',
    birthday: ''
  }

  render() {
    console.log('render');
    
    const { name, career, birthday } = this.state;

    return (
      <div>
        <div id="name">{`我叫${name}`}</div>
        <div>{`我是一名${career}`}</div>
        {
          birthday &&
          (
            <div>{`我的生日是: ${birthday}`}</div>
          )
        }
        <button
          onClick={
            () => {
              this.setState({
                birthday: '1940年11月27日'
              });
            }
          }
        >设置生日</button>
      </div>
    );
  }
}

export default BruceLee;
复制代码

初始化的时候, render方法执行, 于是我们在页面上看到了内容, 此时打印render, 当我们改变了state的值之后, 组件就会重新渲染, 此时render方法会再次执行, 再次打印render, 我们修改state会导致render方法的执行, 也就是修改state会导致组件的渲染

这也是state的作用, 相信大家并不陌生, 但如果是在函数式组件中通过hooks来使用state呢, 那该怎么用, 又会是什么样的一个结果呢

函数式组件

既然我们知道修改classstate会导致render方法执行, 那么首先就应该确定函数式组件中, 它的render方法在哪, 查阅官方文档Hooks FAQ中, 生命周期方法是如何和hooks对应起来的: How do lifecycle methods correspond to Hooks?一文我们发现:

render: This is the function component body itself.

render方法: 函数式组件的函数体就是render方法

由此看来, 如果我们改变了state的值, 那函数式组件就会重新执行喽? 而这又分成两种情况, 接下来我们一个一个来看

不需要从props中去初始化state

import React, { useState } from 'react';

const BruceLeeHook = () => {
  const [ name, setName ] = useState('李小龙');
  const [ career, setCareer ] = useState('武术家, 演员');
  const [ birthday, setBirthday ] = useState('');

  console.log('render');

  return (
    <div>
      <div id="name">{`我叫${name}`}</div>
      <div>{`我是一名${career}`}</div>
      {
        birthday &&
        (
          <div>{`我的生日是: ${birthday}`}</div>
        )
      }
      <button
        onClick={
          () => {
            setBirthday('1940年11月27日');
          }
        }
      >设置生日</button>
    </div>
  );
};

export default BruceLeeHook;
复制代码

初始化的时候函数执行, 页面渲染, 同时打印了render, 没问题, 符合预期, 以及当我们修改state的时候, 发现函数确实重新执行了, 页面重新渲染, 并且又打印了一次render, 印证了我们的推测

从props中去初始化state

我们知道, 在class组件中, 如果遇到需要从外部props初始化state, 应该这么写:

  constructor(props) {
    super(props);

    const { name } = props;

    this.state = {
      name
    }
  }
复制代码

那如果是函数式组件需用从props中去初始化state的话应该怎么写呢?

相信细心的你已经发现了上面那篇文章中的第一句话:

constructor: Function components don’t need a constructor. You can initialize the state in the useState call. If computing the initial state is expensive, you can pass a function to useState.

构造函数: 函数式组件不需要构造函数, 你可以在useState的调用中去初始化state, 如果计算state会有较大的开销, 那你可以传一个函数到useState

简而言之就是: 函数式组件中, 如果需要从外部props中来初始化state, 那么可以传一个函数到useState

查看useStated.ts文件可以发现:

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
复制代码

参数initialState可以是一个函数, 这个函数的返回值就是我们的initialState, 那我们就可以这样写:

父组件:

const Demo = () => {
  const handleInitName = () => '李小龙';

  console.log('demo');
  return (
    <div>
      <BruceLeeHook
        handleInitName={handleInitName}
      />
    </div>
  );
};

export default Demo;
复制代码

子组件:

import React, { useState } from 'react';

const BruceLeeHook = ({ handleInitName }) => {
  const [ name, setName ] = useState(handleInitName);
  const [ career, setCareer ] = useState('武术家, 演员');
  const [ birthday, setBirthday ] = useState('');

  console.log('render');

  return (
    <div>
      <div id="name">{`我叫${name}`}</div>
      <div>{`我是一名${career}`}</div>
      {
        birthday &&
        (
          <div>{`我的生日是: ${birthday}`}</div>
        )
      }
      <button
        onClick={
          () => {
            setBirthday('1940年11月27日');
          }
        }
      >设置生日</button>
    </div>
  );
};

export default BruceLeeHook;
复制代码

此时查看页面, 渲染的结果和第一种写法一样, 同时也能正常的修改state触发页面的重绘

class和函数式组件如何选择

当我们有了选择, 那就涉及到如何做选择的问题, 一个项目中可以两种都共存, 这是没问题的, 但细化到某一个组件, 那就有且仅有一个写法, 要么写成class的形式, 要么写成函数式组件

此时个人觉得, 如果一个组件是纯展示型的组件, 那么使用函数式组件再好不过, 代码精简, 也能满足需求

但当一个页面是顶级页面, 顶级组件, 或者其他组件, 和顶级页面/组件一样涉及比较多的逻辑处理, 要管理众多的state的时候, 那么class更合适, 因为众多的state和逻辑处理意味着多种生命周期方法的处理, 以及prevProps prevState的判断与管理, 此时函数式组件就显得有点力不从心了, 当然不是意味着无法实现, 只是相对class来说上手有些难度, 因此这个时候, 个人更推荐使用class形式的组件

这就是这篇文章的全部内容了, 如果你觉得这篇文章写得还不错, 别忘了给我点个赞, 如果你觉得对你有帮助, 可以点个收藏, 以备不时之需

参考文献:

  1. Using the State Hook
  2. Function and Class Components
  3. How do lifecycle methods correspond to Hooks?
分类:
前端
标签:
分类:
前端
标签: