15

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/d...

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/d...

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

20190313113615.png

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

20190313113627.png

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

20190313113753.png

文档:https://zh-hans.reactjs.org/d...

比方说我们使用 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 配合使用真的非常爽⛄

参考:


JS菌
6.4k 声望2k 粉丝