🔱 快速上手三大基础 React Hooks

8,837 阅读6分钟

Hooks 出了有段时间了,不知盆友们有在项目中开始使用了吗❓如果还没了解的童鞋,可以瞧瞧这篇文章,对比看下三大基础 Hooks 和传统 class 组件的区别和用法吧😙

我们所指的三个基础 Hooks 是:

  • useState 在函数式组件内维护 state
  • useEffect 函数式组件内有副作用的调用与 componentDidMountcomponentDidUpdate 类似但又有所区别
  • useContext 监听 provider 更新变化

useState

useState 允许我们在函数式组件中维护 state,传统的做法需要使用类组件。举个例子🌰,我们需要一个输入框,随着输入框内容的改变,组件内部的 label 标签显示的内容也同时改变。下面是两种不同的写法:

不使用 useState

import React from "react";
// 1
export class ClassTest extends React.Component {
  // 2
  state = {
    username: this.props.initialState
  }
  // 3
  changeUserName(val) {
    this.setState({ username: val })
  }
  // 4
  render() {
    return (
      <div>
        <label style={{ display: 'block' }} htmlFor="username">username: {this.state.username}</label>
        <input type="text" name="username" onChange={e => this.changeUserName(e.target.value)} />
      </div>
    )
  }
}
  1. 我们需要定义一个类并从 React.Component 处继承
  2. 还需要初始化一个 state
  3. 初始化改变 state 的方法
  4. 最后还要使用 render 函数返回 JSX

✅使用 useState

// 1
import React, { useState } from "react";

export function UseStateTest({ initialState }) {
  // 2
  let [username, changeUserName] = useState(initialState)
  // 3
  return (
    <div>
      <label style={{ display: 'block' }} htmlFor="username">username: {username}</label>
      <input type="text" name="username" onChange={e => changeUserName(e.target.value)} />
    </div>
  )
}

在父组件中使用:

import React from "react";
// 引入组件
import { UseStateTest } from './components/UseStateTest'

// 4
const App = () => (
  <div>
    <UseStateTest initialState={'initial value'} />
  </div>
)

export default App;
  1. 需要从 react 中引入 useState 这个📦
  2. 使用 useState 方法,接收一个初始化参数,定义 state 变量,以及改变 state 的方法
  3. 在需要使用的地方直接使用 state 这个变量即可,增加事件处理函数触发改变 state 的方法
  4. 在父组件中调用,通过 props 传递 initialState 初始化值

useState 方法替换掉原有的 class 不仅性能会有所提升,而且可以看到代码量减少很多,并且不再需要使用 this,所以能够维护 state 的函数式组件真的很好用😀

  • class classTest extends React.Components {} 🔜 function UseStateTest(props) {} 函数式组件写法
  • this.state.username 🔜 username 使用 state 不需要 this
  • this.setState({ username: '' }) 🔜 changeUserName('') 改变 state 也不需要书写 setState 方法

文档说明:https://zh-hans.reactjs.org/docs/hooks-state.html

useEffect

useEffect 是专门用来处理副作用的,获取数据、创建订阅、手动更改 DOM 等这都是副作用。你可以想象它是 componentDidMountcomponentDidUpdatecomponentWillUnmount 的结合。

举个例子🌰🌰,比方说我们创建一个 div 标签,每当点击就会发送 http 请求并将页面 title 改为对应的数值:

import React from 'react'
// 1
import { useState, useEffect } from 'react'

export function UseEffectTest() {
    let [msg, changeMsg] = useState('loading...')
    // 2
    async function getData(url) { // 获取 json 数据
        return await fetch(url).then(d => d.json())
    }
    // 3
    async function handleClick() { // 点击事件改变 state
        let data = await getData('https://httpbin.org/uuid').then(d => d.uuid)
        changeMsg(data)
    }
    // 4
    useEffect(() => { // 副作用
        document.title = msg
    })
    return (
        <div onClick={() => handleClick()}>{msg}</div>
    )
}
  1. 首先从 react 中引入 useEffect 😳
  2. 然后创建获取数据的 getData 方法
  3. 创建事件处理函数 handleClick
  4. 使用 useEffect 处理副作用:改变页面的 title

如果使用传统的类组件的写法:

import React from 'react'
// 1
export class ClassTest extends React.Component {
    // 2
    state = {
        msg: 'loading...'
    }
    // 3
    async getData(url) { // 获取 json 数据
        return await fetch(url).then(d => d.json())
    }
    handleClick = async () => { // 点击事件改变 state
        let data = await this.getData('https://httpbin.org/uuid').then(d => d.uuid)
        this.setState({ msg: data })
    }
    // 4
    componentDidMount() {
        document.title = this.state.msg
    }
    componentDidUpdate() {
        document.title = this.state.msg
    }
    // 5
    render() {
        return (
            <div onClick={this.handleClick}>{this.state.msg}</div>
        )
    }
}
  1. 首先创建类 ClassTest
  2. 初始化 state
  3. 定义获取数据方法和事件处理函数
  4. componentDidMountcomponentDidUpdate 阶段改变 document.title
  5. 最后通过 render 函数渲染

这一堆东西写完人都睡着了💤

使用 useEffect 不仅去掉了部分不必要的东西,而且合并了 componentDidMountcomponentDidUpdate 方法,其中的代码只需要写一遍。😀

第一次渲染和每次更新之后都会触发这个钩子,如果需要手动修改自定义触发规则

见文档:https://zh-hans.reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

另外,官网还给了一个订阅清除订阅的例子:

20190313113615.png

使用 useEffect 直接 return 一个函数即可:

20190313113627.png

返回的函数是选填的,可以使用也可以不使用:

20190313113753.png

文档:https://zh-hans.reactjs.org/docs/hooks-effect.html#recap

比方说我们使用 useEffect 来解绑事件处理函数:

useEffect(() => {
    window.addEventListener('keydown', handleKeydown);
    return () => {
        window.removeEventListener('keydown', handleKeydown);
    }
})

useContext

useContext 的最大的改变是可以在使用 Consumer 的时候不必在包裹 Children 了,比方说我们先创建一个上下文,这个上下文里头有一个名为 usernamestate,以及一个修改 username 的方法 handleChangeUsername

创建上下文

不使用 useState:

不使用 state hooks 的代码如下:

import React, { createContext } from 'react'

// 1. 使用 createContext 创建上下文
export const UserContext = new createContext()

// 2. 创建 Provider
export class UserProvider extends React.Component {

    handleChangeUsername = (val) => {
        this.setState({ username: val })
    }

    state = {
        username: '',
        handleChangeUsername: this.handleChangeUsername
    }

    render() {
        return (
            <UserContext.Provider value={this.state}>
                {this.props.children}
            </UserContext.Provider>
        ) 
    }
}

// 3. 创建 Consumer
export const UserConsumer = UserContext.Consumer

看看我们做了什么:

  1. 首先我们使用 createContext 创建了上下文
  2. 然后我们创建 Provider;里头定义了 handleChangeUsername 方法和 usernamestate,并返回一个包裹 this.props.childrenProvider
  3. 最后我们再返回 UserContext.Consumer

代码比较冗长,可以使用上文提到的 useState 对其进行精简:

✅使用 useState:

使用 state hooks

import React, { createContext, useState } from 'react'

// 1. 使用 createContext 创建上下文
export const UserContext = new createContext()

// 2. 创建 Provider
export const UserProvider = props => {
    let [username, handleChangeUsername] = useState('')
    return (
        <UserContext.Provider value={{username, handleChangeUsername}}>
            {props.children}
        </UserContext.Provider>
    )
}

// 3. 创建 Consumer
export const UserConsumer = UserContext.Consumer

使用 useState 创建上下文更加简练。

使用上下文

上下文定义完毕后,我们再来看使用 useContext 和不使用 useContext 的区别是啥🎁:

不使用 useContext:

import React  from "react";
import { UserConsumer, UserProvider } from './UserContext'

const Pannel = () => (
  <UserConsumer>
    {/* 不使用 useContext 需要调用 Consumer 包裹 children */}
    {({ username, handleChangeUsername }) => (
      <div>
        <div>user: {username}</div>
        <input onChange={e => handleChangeUsername(e.target.value)} />
      </div>
    )}
  </UserConsumer>
)

const Form = () => <Pannel></Pannel>

const App = () => (
  <div>
    <UserProvider>
      <Form></Form>
    </UserProvider>
  </div>
)

export default App;

✅使用 useContext:

只需要引入 UserContext,使用 useContext 方法即可:

import React, { useContext }  from "react"; // 1
import { UserProvider, UserContext } from './UserContext' // 2

const Pannel = () => {
  const { username, handleChangeUsername } = useContext(UserContext) // 3
  return (
    <div>
      <div>user: {username}</div>
      <input onChange={e => handleChangeUsername(e.target.value)} />
    </div>
  )
}

const Form = () => <Pannel></Pannel>

// 4
const App = () => (
  <div>
    <UserProvider>
      <Form></Form>
    </UserProvider>
  </div>
)

export default App;

看看做了啥:

  1. 首先第一步引入useContext
  2. 然后引入 UserProvider 以及上下文 UserContext
  3. 再需要使用的组件中调用 useContext 方法获取 state
  4. 当然前提是要在父组件中使用 UserProvider 嵌套主 children

这样通过 useContextuseState 就重构完毕了,看起来代码又少了不少😄

以上,三个基础的 Hooks 入门就讲解完毕了,上手就是这样,函数式组件和 Hooks 配合使用真的非常爽⛄

参考:

  • https://codeburst.io/quick-intro-to-react-hooks-6dd8ecb898ed
  • https://reactjs.org/docs/hooks-reference.html