什么是钩子(hooks)?

  • 消息处理的一种方法,用来监视指定程序
  • 函数组件中需要处理副作用,可以用钩子把外部代码”钩”进来
  • Hooks的目的是为了给函数式组件加上状态
  • 不再需要类组件

    useState

    1. const App: React.FC = (props) => {
    2. const [count, setCount] = useState(0)
    3. return (
    4. <div className={styles.app}>
    5. <button
    6. onClick={() => {
    7. setCount(count + 1)
    8. }}
    9. >
    10. {count}
    11. </button>
    12. </div>
    13. )
    14. }

    setCount是异步的,即使有多个setCount也只会执行一次

    sideEffect

    纯函数:

  1. 函数与外界的只有唯一一个渠道进行沟通,通过传入参数和返回值进行沟通。
  2. 相同的传入参数永远只会有相同的返回值。

副作用:

  1. 除了返回值,还做了其他事情,例如修改全局变量

useEffect

  • 如果不传第二个参数,每次渲染都会调用useEffect, 相当于componentDidUpdate
  • 第二个参数传[], 相当于componentDidMount
  • useEffect可以return一个清理函数,相当于componentWillUnmount时调用

    1. function Example() {
    2. const [count, setCount] = useState(0);
    3. useEffect(() => {
    4. document.title = `You clicked ${count} times`;
    5. });
    6. return (
    7. <div>
    8. <p>You clicked {count} times</p>
    9. <button onClick={() => setCount(count + 1)}>
    10. Click me
    11. </button>
    12. </div>
    13. );
    14. }

    在useEffect中使用async/await

    直接将回调函数写成async/await是不行的,因为async默认返回的类型是Promise,与useEffect冲突
    image.png
    正确写法是先声明一个函数再调用

    1. useEffect(() => {
    2. const getRobotGallery = async () => {
    3. const response = await fetch('https://jsonplaceholder.typicode.com/users')
    4. const data = await response.json()
    5. setRobotGallery(data)
    6. }
    7. getRobotGallery()
    8. }, [])

    处理loading

    声明一个loading标记

    1. const [loading, setLoading] = useState<boolean>(false)
    2. useEffect(() => {
    3. const fetchData = async () => {
    4. setLoading(true)
    5. const response = await fetch('https://jsonplaceholder.typicode.com/users')
    6. const data = await response.json()
    7. setRobotGallery(data)
    8. setLoading(false)
    9. }
    10. fetchData()
    11. }, [])

    根据标记显示

    1. {!loading ? (
    2. <div className={styles.robotList}>
    3. {robotGallery.map((r: any) => (
    4. <Robot id={r.id} name={r.name} email={r.email} />
    5. ))}
    6. </div>
    7. ) : (
    8. <div>loading 数据加载中...</div>
    9. )}

    处理error

    用try…catch

    1. const [errorMsg, setErrorMsg] = useState('')
    2. useEffect(() => {
    3. const fetchData = async () => {
    4. setLoading(true)
    5. try {
    6. const response = await fetch(
    7. 'https://jsonplaceholder.typicode.com/users'
    8. )
    9. const data = await response.json()
    10. setRobotGallery(data)
    11. } catch (e: any) {
    12. setErrorMsg(e.message)
    13. }
    14. setLoading(false)
    15. }
    16. fetchData()
    17. }, [])

    context和useContext

    context可以避免数据通过props层层传递
    在index.tsx中注册context ```jsx const defaultContextValue = { name: ‘jack’, } export const appContext = React.createContext(defaultContextValue) // 参数是默认值

ReactDOM.render( , document.getElementById(‘root’) )

  1. 在组件中消费context, comsumer必须使用函数形式传递value
  2. ```jsx
  3. import { appContext } from '../index'
  4. const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
  5. return (
  6. <appContext.Consumer>
  7. {(value) => (
  8. <div className={styles.cardContainer}>
  9. <img src={`https://robohash.org/${id}`} alt="robot" />
  10. <h2>{name}</h2>
  11. <p>{email}</p>
  12. <p>作者:{value.name}</p>
  13. </div>
  14. )}
  15. </appContext.Consumer>
  16. )
  17. }

用useContext代替consumer

  1. import { appContext } from '../index'
  2. import { useContext } from 'react'
  3. const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
  4. const user = useContext(appContext)
  5. return (
  6. <div className={styles.cardContainer}>
  7. <img src={`https://robohash.org/${id}`} alt="robot" />
  8. <h2>{name}</h2>
  9. <p>{email}</p>
  10. <p>作者:{user.name}</p>
  11. </div>
  12. )
  13. }

context是局部的全局变量
useContext可以和useReducer或者useState配合,将reducer或者state传递到任意组件
在class组件中只能使用context.consumer

封装context provider组件

  1. import React, { useState } from 'react'
  2. interface DefaultContextValue {
  3. name: string
  4. shoppingCartItems: { id: number; name: string }[]
  5. }
  6. const defaultContextValue: DefaultContextValue = {
  7. name: 'jack',
  8. shoppingCartItems: [],
  9. }
  10. interface StateContext {
  11. state: DefaultContextValue
  12. setState:
  13. | React.Dispatch<React.SetStateAction<DefaultContextValue>>
  14. | undefined
  15. }
  16. export const appContext = React.createContext<StateContext>({
  17. state: defaultContextValue,
  18. setState: undefined,
  19. })
  20. export const AppStateProvider: React.FC = (props) => {
  21. const [state, setState] = useState(defaultContextValue)
  22. return (
  23. <appContext.Provider value={{ state, setState }}>
  24. {props.children}
  25. </appContext.Provider>
  26. )
  27. }

这里为了把state和setState作为一个对象传给一个context,用typescript才这样写
也可以把state和setState分别注册成context, 使用2个provider

  1. import React, { useState } from 'react'
  2. interface DefaultContextValue {
  3. name: string
  4. shoppingCartItems: { id: number; name: string }[]
  5. }
  6. const defaultContextValue: DefaultContextValue = {
  7. name: 'jack',
  8. shoppingCartItems: [],
  9. }
  10. export const appContext = React.createContext(defaultContextValue)
  11. export const appSetStateContext = React.createContext<React.Dispatch<React.SetStateAction<DefaultContextValue>>
  12. | undefined>(undefined)
  13. export const AppStateProvider: React.FC = (props) => {
  14. const [state, setState] = useState(defaultContextValue)
  15. return (
  16. <appContext.Provider value={state}>
  17. <appSetStateContext.Provider value={setState}>
  18. {props.children}
  19. </appSetStateContext.Provider>
  20. </appContext.Provider>
  21. )
  22. }

实现加入购入车功能

  1. const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
  2. const store = useContext(appContext)
  3. const addToCart = () => {
  4. if (store.setState) {
  5. store.setState({
  6. ...store.state,
  7. shoppingCartItems: [...store.state.shoppingCartItems, { id, name }],
  8. })
  9. }
  10. }
  11. return (
  12. <div className={styles.cardContainer}>
  13. <img src={`https://robohash.org/${id}`} alt="robot" />
  14. <h2>{name}</h2>
  15. <p>{email}</p>
  16. <p>作者:{store.state.name}</p>
  17. <button onClick={addToCart}>加入购物车</button>
  18. </div>
  19. )
  20. }

高阶组件Higher-Order Components(HOC)

高阶组件是参数为组件,返回值为新组件的函数。高阶组件都以with开头
如果两个组件有相似的代码逻辑,应使用高阶组件封装,提高代码复用率
文档
可以返回函数式组件或者类组件

  1. import { appContext } from '../AppState'
  2. import { useContext } from 'react'
  3. import { RobotProp } from './Robot'
  4. export const withAddToCart = (
  5. ChildComponent: React.ComponentType<RobotProp>
  6. ) => {
  7. // return class extends React.Component {}
  8. return (props: any) => {
  9. const store = useContext(appContext)
  10. const addToCart = (id: number, name: string) => {
  11. if (store.setState) {
  12. store.setState({
  13. ...store.state,
  14. shoppingCartItems: [...store.state.shoppingCartItems, { id, name }],
  15. })
  16. }
  17. }
  18. return <ChildComponent {...props} addToCart={addToCart} store={store} />
  19. }
  20. }

使用

  1. import styles from './Robot.module.css'
  2. import { StateContext } from '../AppState'
  3. import { withAddToCart } from './AddToCart'
  4. export interface RobotProp {
  5. id: number
  6. name: string
  7. email: string
  8. store: StateContext
  9. addToCart: (id: number, name: string) => void
  10. }
  11. const Robot: React.FC<RobotProp> = ({ id, name, email, store, addToCart }) => {
  12. return (
  13. <div className={styles.cardContainer}>
  14. <img src={`https://robohash.org/${id}`} alt="robot" />
  15. <h2>{name}</h2>
  16. <p>{email}</p>
  17. <p>作者:{store.state.name}</p>
  18. <button onClick={() => addToCart(id, name)}>加入购物车</button>
  19. </div>
  20. )
  21. }
  22. export default withAddToCart(Robot)

自定义hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。
自定义hook可以代替HOC高阶组件

  1. export const useAddToCart = () => {
  2. const store = useContext(appContext)
  3. const addToCart = (id: number, name: string) => {
  4. if (store.setState) {
  5. store.setState({
  6. ...store.state,
  7. shoppingCartItems: [...store.state.shoppingCartItems, { id, name }],
  8. })
  9. }
  10. }
  11. return {
  12. store,
  13. addToCart,
  14. }
  15. }

使用

  1. import styles from './Robot.module.css'
  2. import { useAddToCart } from './AddToCart'
  3. export interface RobotProp {
  4. id: number
  5. name: string
  6. email: string
  7. }
  8. const Robot: React.FC<RobotProp> = ({ id, name, email }) => {
  9. const { store, addToCart } = useAddToCart()
  10. return (
  11. <div className={styles.cardContainer}>
  12. <img src={`https://robohash.org/${id}`} alt="robot" />
  13. <h2>{name}</h2>
  14. <p>{email}</p>
  15. <p>作者:{store.state.name}</p>
  16. <button onClick={() => addToCart(id, name)}>加入购物车</button>
  17. </div>
  18. )
  19. }
  20. export default Robot