React 之 Hooks

一、Hook 简介

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

什么是hook?

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数

使用规则

1、只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用
2、只能在 React 的函数组件 中调用 Hook。不要在其他 JavaScript 函数中调用

二、常见 hooks

1、useState

useState 允许你在函数组件中添加状态

1.1 基本用法

1、组件中导入 useState:

import React, { useState } from 'react';

2、在函数组件内部,调用 useState 并传入一个初始状态值。
useState 会返回一个新的数组

  • 第一个值为当前状态值
  • 第二个值为更新状态的函数
const [state, setState] = useState(initialState);
// state 是当前状态值
// setState 是一个函数,用于更新状态值

代码示例:

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量。
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

声明多个 state 变量

function ExampleWithManyStates() {
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

1.2 函数式更新

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值

代码示例:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

1.3 惰性初始 state

如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

2、useEffect

useEffect 可以让你在函数组件中执行副作用操作

组件挂载和更新时触发

核心代码:
不传第二个参数

  useEffect(() => {
    console.log("组件挂载和更新时触发,相当于 componentDidMount 和 componentDidUpdate");
  });

代码示例:

import React, { useState, useEffect } from "react";

function UseEffect() {
  const [count, setCount] = useState(0);

  // 组件挂载和更新时触发,相当于 componentDidMount 和 componentDidUpdate
  useEffect(() => {
    console.log("组件挂载和更新时触发,相当于 componentDidMount 和 componentDidUpdate");
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default UseEffect;

只在组件挂载时触发

核心代码:
第二个参数直接空数组 []

  useEffect(() => {
    console.log("只在组件挂载时触发,相当于 componentDidMount");
  }, []);

代码示例:

import React, { useState, useEffect } from "react";

function UseEffect() {
  const [count, setCount] = useState(0);

  // 只在组件挂载时触发,相当于 componentDidMount
  useEffect(() => {
    console.log("只在组件挂载时触发,相当于 componentDidMount");
  }, []);


  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default UseEffect;

组件更新时触发

核心代码:
count 值变化时触发

  useEffect(() => {
    console.log("组件更新时触发,相当于 componentDidUpdate");
  }, [count]);
import React, { useState, useEffect } from "react";

function UseEffect() {
  const [count, setCount] = useState(0);

  // 组件更新时触发,相当于 componentDidUpdate
  useEffect(() => {
    console.log("组件更新时触发,相当于 componentDidUpdate");
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default UseEffect;

组件卸载时触发

说明:如果在 effect 中返回一个函数,则 React 将会在卸载时调用它
核心代码:

  useEffect(() => {
    console.log("只在组件挂载时触发");

    return () => {
      console.log("组件卸载时触发");
    };
  }, []);

代码示例:

import React, { useState, useEffect } from "react";

function UseEffect() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("只在组件挂载时触发");
	
	// 返回一个函数,React 将会在执行清除操作时调用它
    return () => {
      console.log("组件卸载时触发");
    };
  }, []);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

export default UseEffect;

3、useContext

用于在函数组件中使用上下文

基本用法

3个步骤:

步骤一:使用 createContext 创建一个 context 对象(MyContext)
步骤二:顶层组件通过 MyContext.Provider,并在 value 上绑定值提供数据
步骤三:底层组件通过 useContext 获取参数

代码示例:

import { createContext, useContext } from "react";


const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};
// 1、使用 createContext 创建一个 context 对象 ThemeContext
const ThemeContext = createContext(themes.dark);

function ThemedButton() {
  // 3、底层组件通过 useContext 获取参数
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      context 样式按钮
    </button>
  );
}

function Toolbar(props: any) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function UseContext() {
  return (
    // 2、顶层组件通过  ThemeContext.Provider,并在 value 上绑定值提供数据
    <ThemeContext.Provider value={themes.light}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

export default UseContext;

4、useReducer

用于在函数组件中使用状态

useState 的替代方案,它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法

语法:

const [state, dispatch] = useReducer(reducer, initialArg, init);

代码示例:

import { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const UseReducer = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <span>Count: {state.count}</span>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
};

export default UseReducer;

5、useCallback

用于在函数组件中缓存一个回调函数

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本

该回调函数仅在某个依赖项改变时才会更新

代码示例:

import { useCallback, useState } from "react";

const UseCallback = () => {
  const [count, setCount] = useState(0);
  const [num, setNum] = useState(2);

  // 当num变化时才会更新
  const incrementCount = useCallback(() => {
    setCount(count + num);
  }, [num]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={incrementCount}>加 count</button>
      <p>当前count步进: {num}</p>
      <button onClick={() => setNum(num + 1)}>加count步进</button>
    </div>
  );
};

export default UseCallback;

6、useMemo

用于在函数组件中缓存计算结果
语法:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

返回一个 memoized 值

把“创建”函数和依赖项数组作为参数传入 useMemo
它仅会在某个依赖项改变时才重新计算 memoized 值。
这种优化有助于避免在每次渲染时都进行高开销的计算
如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值

代码示例:

import React, { useMemo, useState } from "react";

const UseMemo = () => {
  const [num1, setNum1] = useState(1);
  const [num2, setNum2] = useState(2);

  const add = useMemo(() => (a: number, b: number) => a + b, [num1, num2]);

  return (
    <div>
      <div>
        <span>num1: {num1}</span>
        <button onClick={() => setNum1(num1 + 1)}>num1+</button>
        <span>num2: {num2}</span>
        <button onClick={() => setNum2(num2 + 2)}>num2+</button>
      </div>
      <div>求和:{add(num1, num2)}</div>
    </div>
  );
};

export default UseMemo;

7、useRef

用于在函数组件中创建一个 ref 对象,该对象可以用来访问组件的 DOM 节点或属性
语法:

const refContainer = useRef(initialValue);

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)
返回的 ref 对象在组件的整个生命周期内持续存在

代码示例:

import { useRef } from "react";

const UseRef = () => {
  const inputEl = useRef<HTMLInputElement>(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl?.current?.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
};

export default UseRef;

8、useImperativeHandle

用于在函数组件中提供响应式引用

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值
useImperativeHandle 应当与 forwardRef 一起使用:

语法:

useImperativeHandle(ref, createHandle, [deps])

代码示例:

import { useEffect, useRef, useImperativeHandle, forwardRef } from "react";

// 子组件
const FancyInput = forwardRef((props: any, ref: any) => {
  const inputRef: any = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.value = "Hello";
    },
    test: () => {
      console.log("test");
    },
  }));
  return <input ref={inputRef} />;
});

// 父组件
const UseImperativeHandle = () => {
  let ref: any = useRef(null);
  useEffect(() => {
    console.log(ref);
    // 调用子组件的 focus 方法
    ref?.current?.focus();
    // 调用子组件的 test 方法
    ref?.current?.test();
  });
  return (
    <>
      <FancyInput ref={ref} />
    </>
  );
};
export default UseImperativeHandle;

9、useLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
可以使用它来读取 DOM 布局并同步触发重渲染
在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
注:尽可能使用标准的 useEffect 以避免阻塞视觉更新

与 useEffect的区别:
useEffect 是异步触发
useLayoutEffect 是同步触发,适用于需要在DOM更新之前立即执行操作的情况

三、自定义 hook

自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook
作用:通过自定义 Hook,可以将组件逻辑提取到可重用的函数中

自定义一个hook

定义 hook useCount,实现自动 +1 的计数器

const useCount = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount((count) => count + 1);
    }, 2000);

    // 当组件卸载时,清除定时器
    return () => clearInterval(interval);
  }, []);

  return count;
};

使用 自定义hook

在组件中使用自定义 hook

// 组件1
const TextComp1 = () => {
  const count = useCount();

  return (
    <div>
      <h3>这是组件1</h3>
      {count}
    </div>
  );
};

// 组件2
const TextComp2 = () => {
  const count = useCount();

  return (
    <div>
      <h3>这是组件2</h3>
      <p style={{ color: count % 2 === 0 ? "green" : "red" }}>{count}</p>
    </div>
  );
};

// 父组件
const UseCustom = () => {
  return (
    <div>
      <TextComp1 />
      <TextComp2 />
    </div>
  );
};

完整示例的代码:

import { useEffect, useState } from "react";

// 自定义 hook useCount,实现自动 +1 的计数器
const useCount = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCount((count) => count + 1);
    }, 2000);

    // 当组件卸载时,清除定时器
    return () => clearInterval(interval);
  }, []);

  return count;
};

// 组件1
const TextComp1 = () => {
  const count = useCount();

  return (
    <div>
      <h3>这是组件1</h3>
      {count}
    </div>
  );
};

// 组件2
const TextComp2 = () => {
  const count = useCount();

  return (
    <div>
      <h3>这是组件2</h3>
      <p style={{ color: count % 2 === 0 ? "green" : "red" }}>{count}</p>
    </div>
  );
};

// 父组件
const UseCustomHook = () => {
  return (
    <div>
      <TextComp1 />
      <TextComp2 />
    </div>
  );
};

export default UseCustomHook;

推荐1个 github 上的封装的hooks

github地址:

https://github.com/alibaba/hooks

如需下载源码到本地:

git clone https://github.com/alibaba/hooks.git

中文文档:

https://ahooks.js.org/zh-CN/hooks/use-request/index
  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值