React Hooks实在是泰裤辣!

164 阅读6分钟

React Hooks使用笔记

我使用React以前的Class写法以及对this的理解度,写起来实在是不习惯,我本人特别推崇函数式写法,而且我最近使用React官方文档的next.js模板,重新整理常用的 Hooks,进一步的学习使用React。

useState

useState是一个 React Hook,可让您向组件添加状态变量。

const [state, setState] = useState(initialState);
import { useState } from 'react';  

function MyComponent() {  
const [age, setAge] = useState(28);  
const [name, setName] = useState('Taylor');  
const [todos, setTodos] = useState(() => createTodos());  

// ...

传递更新函数

import { useState } from 'react';

export default function Counter() {
  const [age, setAge] = useState(42);

  function increment() {
    setAge(a => a + 1);//注意setAge(age + 1)不能+3
  }

  return (
    <>
      <h1>Your age: {age}</h1>
      <button onClick={() => {
        increment();
        increment();
        increment();
      }}>+3</button>
      <button onClick={() => {
        increment();
      }}>+1</button>
    </>

image.png 如果没有传递更新函数,所以“+3”按钮没有按预期工作

useEffect

useEffect 可以让你在函数组件中执行副作用操作。可让您将组件与外部系统同步。

副作用是指一段和当前执行结果无关的代码,常用的副作用操作如数据获取、设置订阅、手动更改 React 组件中的 DOM。 useEffect 可以接收两个参数,第一个参数是要执行的函数 callback,第二个参数是可选的依赖项数组 dependencies。

useEffect(callback, dependencies)
import {useEffect} from 'react';  
import {createConnection} from './chat.js';  
  
function ChatRoom({roomId}) {  
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');  
    useEffect(() => {  
        const connection = createConnection(serverUrl, roomId);  
        connection.connect();  
        return () => {  
        connection.disconnect();  
        };  
}, [serverUrl, roomId]);  
// ...  
}

监听全局浏览器事件

import { useState, useEffect } from 'react';

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    function handleMove(e) {
      setPosition({ x: e.clientX, y: e.clientY });
    }
    window.addEventListener('pointermove', handleMove);
    return () => {
      window.removeEventListener('pointermove', handleMove);
    };
  }, []);

  return (
    <div style={{
      position: 'absolute',
      backgroundColor: 'pink',
      borderRadius: '50%',
      opacity: 0.6,
      transform: `translate(${position.x}px, ${position.y}px)`,
      pointerEvents: 'none',
      left: -20,
      top: -20,
      width: 40,
      height: 40,
    }} />
  );
}

image.png

用途

useEffect 提供了四种执行副作用的时机:(可同时存在)

  • 每次 render 后执行:不提供第二个依赖项参数。比如 useEffect(() => {});
  • 仅第一次 render 后执行:提供一个空数组作为依赖项。比如 useEffect(() => {}, []);
  • 第一次以及依赖项发生变化后执行:提供依赖项数组。比如 useEffect(() => {}, [deps]);
  • 组件 unmount 后执行:返回一个回调函数。比如 useEffect() => { return () => {} }, [])。

useCallback

useCallback 定义的回调函数只会在依赖项改变时重新声明这个回调函数,这样就保证了组件不会创建重复的回调函数。而接收这个回调函数作为属性的组件,也不会频繁地需要重新渲染

import { useCallback } from 'react';  
export default function ProductPage({ productId, referrer, theme }) {  
const handleSubmit = useCallback((orderDetails) => {  
    post('/product/' + productId + '/buy', { referrer,  orderDetails});  
}, [productId, referrer]);

useMemo

useMemo是一个 React Hook,可让您在重新渲染之间缓存计算结果。 useMemo 定义的创建函数只会在某个依赖项改变时才重新计算,有助于每次渲染时不会重复的高开销的计算,而接收这个计算值作为属性的组件,也不会频繁地需要重新渲染

import { useMemo } from 'react';  
  
function TodoList({ todos, tab }) {  
    const visibleTodos = useMemo( 
    () => filterTodos(todos, tab), 
    [todos, tab] 
  );  
// ...  
}

你会经常看到useMemo旁边useCallback。当您尝试优化子组件时,它们都很有用。他们让你记住(或者,换句话说,缓存)你传递的东西:

import {useMemo, useCallback} from 'react';  
  
function ProductPage({productId, referrer}) {  
    const product = useData('/product/' + productId);  
  
    const requirements = useMemo(() => { // Calls your function and caches its result  
        return computeRequirements(product);  
    }, [product]);  
  
    const handleSubmit = useCallback((orderDetails) => { // Caches your function itself  
        post('/product/' + productId + '/buy', {  
        referrer,  
        orderDetails,  
        });  
    }, [productId, referrer]);  
  
return (  
    <div className={theme}>  
    <ShippingForm requirements={requirements} onSubmit={handleSubmit}/>  
    </div>  
  );  
}

useRef

useRef 用于返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue

useRef 创建的 ref 对象就是一个普通的 JavaScript 对象,而 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象

import { useRef } from 'react';  

function MyComponent() {  
    const intervalRef = useRef(0);  
    const inputRef = useRef(null);  

// ...

绑定 DOM 元素

使用 useRef 创建的 ref 对象可以作为访问 DOM 的方式,将 ref 对象以 <div ref={myRef} /> 形式传入组件,React 会在组件创建完成后会将 ref 对象的 .current 属性设置为相应的 DOM 节点

聚焦文字输入框

import React, { useRef } from 'react'

export default function FocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

image.png

绑定可变值

useRef 创建的 ref 对象同时可以用于绑定任何可变值,通过手动给该对象的.current 属性设置对应的值即可

秒表

import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        Start
      </button>
      <button onClick={handleStop}>
        Stop
      </button>
    </>
  );
}

image.png

useContext

在组件的顶层调用useContext来读取和订阅上下文

import { useContext } from 'react';

function MyComponent() {
  const theme = useContext(ThemeContext);
  // ...

通过上下文 来更新数据

import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <Form />
      <label>
        <input
          type="checkbox"
          checked={theme === 'dark'}
          onChange={(e) => {
            setTheme(e.target.checked ? 'dark' : 'light')
          }}
        />
        Use dark mode
      </label>
    </ThemeContext.Provider>
  )
}

function Form({ children }) {
  return (
    <Panel title="Welcome">
      <Button>Sign up</Button>
      <Button>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = useContext(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ children }) {
  const theme = useContext(ThemeContext);
  const className = 'button-' + theme;
  return (
    <button className={className}>
      {children}
    </button>
  );
}

useReducer

useReducer是一个 React Hook,可让您向组件添加reducer 。

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

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

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

const initialState = { name: 'Taylor', age: 42 };

export default function Form() {
  const [state, dispatch] = useReducer(reducer, initialState);

  function handleButtonClick() {
    dispatch({ type: 'incremented_age' });
  }

  function handleInputChange(e) {
    dispatch({
      type: 'changed_name',
      nextName: e.target.value
    }); 
  }

  return (
    <>
      <input
        value={state.name}
        onChange={handleInputChange}
      />
      <button onClick={handleButtonClick}>
        Increment age
      </button>
      <p>Hello, {state.name}. You are {state.age}.</p>
    </>
  );
}

自定义Hooks

1. 如何创建自定义 Hooks?

自定义 Hooks 就是函数,它有 2 个特征区分于普通函数:

  • 名称以 “use” 开头;
  • 函数内部调用其他的 Hook。

示例如下:

import { useState, useCallback } from 'react'

function useCounter() {
  // 定义 count 这个 state 用于保存当前数值
  const [count, setCount] = useState(0)
  // 实现加 1 的操作
  const increment = useCallback(() => setCount(count + 1), [count])
  // 实现减 1 的操作
  const decrement = useCallback(() => setCount(count - 1), [count])
  // 重置计数器
  const reset = useCallback(() => setCount(0), [])

  // 将业务逻辑的操作 export 出去供调用者使用
  return { count, increment, decrement, reset }
}

// 组件1
function MyComponent1() {
  const { count, increment, decrement, reset } = useCounter()
}

// 组件2
function MyComponent2() {
  const { count, increment, decrement, reset } = useCounter()
}

以上代码通过自定义 Hooks useCounter,轻松的在 MyComponent1 组件和 MyComponent2 组件之间复用业务逻辑。

2. 自定义 Hooks 库 - react-use

React 官方提供了 react-use 库,其中封装了大量可直接使用的自定义 Hooks,帮助我们简化组件内部逻辑,提高代码可读性、可维护性。

其中我们常用的自定义 Hooks 有:

  • useLocation 和 useSearchParam:跟踪页面导航栏位置状态;
  • useScroll:跟踪 HTML 元素的滚动位置;
  • useScrolling:跟踪 HTML 元素是否正在滚动;
  • useAsync, useAsyncFn, and useAsyncRetry:解析一个 async 函数;
  • useTitle:设置页面的标题。

可至 react-use 官网学习使用。 React 官方提供了 react-use 库,其中封装了大量可直接使用的自定义 Hooks,帮助我们简化组件内部逻辑,提高代码可读性、可维护性。

参考资料