React Hooks学习记录

76 阅读3分钟

useRef

1.存储变量

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>
    </>
  );
}

注意:不要在组件渲染期间读或者写ref.current! 这违背了React纯函数的设计理念

//错误
function MyComponent() {

  // 🚩 Don't write a ref during rendering

  myRef.current = 123;

  // 🚩 Don't read a ref during rendering

  return <h1>{myOtherRef.current}</h1>;

}

//正确
function MyComponent() {  

    useEffect(() => {  


    // ✅ You can read or write refs in effects  


    myRef.current = 123;  


    });  


    function handleClick() {  


    // ✅ You can read or write refs in event handlers  


    doSomething(myOtherRef.current);  


    }  

}

image.png

  • 渲染是一种 计算过程 ,它不应该试图“做”其他事(比如获取DOM改变其类名)。
  • React 无法保证组件函数以任何特定的顺序执行,因此你无法通过设置变量在它们之间进行通信。所有的交流都必须通过 props 进行。

2.操作Dom

当你将 ref 放在像 <input /> 这样输出浏览器元素的内置组件上时,React 会将该 ref 的 current 属性设置为相应的 DOM 节点(例如浏览器中实际的 <input /> )。

但是,如果你尝试将 ref 放在 你自己的 组件上,例如 <MyInput />,默认情况下你会得到 null。这个示例演示了这种情况。 import { useRef } from 'react';

//错误
function MyInput(props) {
  return <input {...props} />;
}
export default function MyForm() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }
  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

//正确
//一般来说react不允许自己创建的组件暴露自内部的DOM,但是子组件包裹上forwardRef就可以了
import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  return <input {...props} ref={ref} />;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

3.避免重复创建ref里面内容

//若new VideoPlayer()耗时较长,不合适
function Video() {  
    const playerRef = useRef(new VideoPlayer());  
// ...

//较优方案——不建议在渲染期间修改ref,但该处仅初始化执行一次,无伤大雅
function Video() {  
    const playerRef = useRef(null);  
    if (playerRef.current === null) {  
    playerRef.current = new VideoPlayer();  
}

useCallback

1.避免组件的重渲染

By default, when a component re-renders, React re-renders all of its children recursively.

useMemo caches the result of calling your function. useCallback caches the function itself.

Caching a function with useCallback is only valuable in a few cases:

  • You pass it as a prop to a component wrapped in memo. You want to skip re-rendering if the value hasn’t changed. Memoization lets your component re-render only if dependencies changed.
  • The function you’re passing is later used as a dependency of some Hook. For example, another function wrapped in useCallback depends on it, or you depend on this function from useEffect.
//ProductPage.js

function ProductPage({ productId, referrer, theme }) {  
// Tell React to cache your function between re-renders...  
const handleSubmit = useCallback((orderDetails) => {  
        post('/product/' + productId + '/buy', {  
            referrer,  
            orderDetails,  
        });  
    }, [productId, referrer]); // ...so as long as these dependencies don't change...  
return (  
    <div className={theme}>  
    {/* ...ShippingForm will receive the same props and can skip re-rendering */}  
    <ShippingForm onSubmit={handleSubmit} /> 
    </div>  
);  
}

//ShippingForm.js
import { memo } from 'react';  
const ShippingForm = memo(function ShippingForm({ onSubmit }) {  
// ...  
});

2.从记忆回调更新状态

function TodoList() {  
    const [todos, setTodos] = useState([]);  
    const handleAddTodo = useCallback((text) => {  
        const newTodo = { id: nextId++, text };  
        setTodos([...todos, newTodo]);  
    }, [todos]);  
// ...

VS

function TodoList() {  
    const [todos, setTodos] = useState([]);  
    const handleAddTodo = useCallback((text) => {  
        const newTodo = { id: nextId++, text };  
        setTodos(todos => [...todos, newTodo]);  
    }, []); // ✅ No need for the todos dependency  
// ...

3.防止副作用被频繁触发

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // ✅ Only changes when roomId changes
  useEffect(() => {
    const options = createOptions();
    const connection = createConnection();
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // ✅ Only changes when createOptions changes
  // ...
  
  //进一步优化
  
 function ChatRoom({ roomId }) {  
    const [message, setMessage] = useState('');  
        useEffect(() => {  
            function createOptions() { // ✅ No need for useCallback or function dependencies!  
                return {  
                    serverUrl: 'https://localhost:1234',  
                    roomId: roomId 
                    };  
        } 
    const options = createOptions();  
    const connection = createConnection();  
    connection.connect();  
    return () => connection.disconnect();  
  }, [roomId]); // ✅ Only changes when roomId changes  
// ...

4.优化自定义hook

useContext

import {useContext,createContext } from "react";
const ThemeContext=createContext(null)
function App() { 
  return (
    <ThemeContext.Provider value={'dark'}>
      <Form/> 
    </ThemeContext.Provider>    
  );
}
//Form组件及其子组件都可以通过useContext拿到themeContext的值
//const theme=useContext(ThemeContext)