Skip to content

react 实践总结

常见问题

useEffect

useEffect 默认执行

  • 默认执行两次
    • 在 React 的开发模式下,useEffect 默认执行两次
    • <React.StrictMode> 注释掉关闭
  • 默认触发一次
    • useEffect(() => {},[a]) a没有变化,也触发一次

useEffect测试

  • 基础测试:第二个参数依赖,返回清除函数,以及打印顺序
查看代码
jsx
import { useEffect, useState } from 'react';
export default function Child1 () {
  {/*
    测试 1.1
      useEffect 第二个参数依赖
    测试 1.2
      返回清除函数,以及打印顺序
  */}
  const [state, setState] = useState(0);
  console.log('render')
  useEffect(() => {
    console.log('useEffect');
  });

  useEffect(() => {
    console.log('useEffect1', state);
  }, [state]);

  useEffect(() => {
    console.log('useEffect2', state);
    return () => {
      console.log('useEffect2 return');
    };
  }, [state]);

  return <>
    <div>1.1 useEffect 第二个参数依赖</div>
    <div>1.2 返回清除函数,以及打印顺序</div>
    <button onClick={() => setState(state + 1)}>setState</button>
  </>;
}
  • 第二个参数没有传入,打印
查看代码
jsx
import { useEffect, useState } from 'react';
export default function Child2 () {
  {/*
    测试
      与 useCallback 对比下,即使useEffect第二个参数没有传入,打印的值都是新值
  */}
  const [state, setState] = useState(0);
  const [state1, setState1] = useState(0);
  const [state2, setState2] = useState(0);
  useEffect(()=>{
    // 这里打印是新值
    console.log(state1)
    setState1(() => {
      // 这里打印是新值
      console.log(state1)
      return state1 + 1
    })
    setState2((value) => {
      // 这里打印是新值
      console.log(value)
      return value + 1
    })
  },[state])
  return <>
    <div>2 useCallback 即使useEffect第二个参数没有传入,打印的值都是新值</div>
    state:<button onClick={() => setState(state + 1)}>{state}</button>
    state1:<button onClick={() => setState1(state1 + 1)}>{state1}</button>
    state2:<button onClick={() => setState2(state2 + 1)}>{state2}</button>
  </>
}

useCallback

useCallback 中useState 取到的是旧值

  • useCallback(() => {console.log(b)},[a]) 不把 b 放到依赖数组时,取到的是旧值?
    • 因为 React 的函数组件里,闭包会捕获当时渲染时的变量值。
    • useCallback 的函数体里用到的 count 定义时的那一份(旧值),不会随着 count 变化而更新。
查看代码
jsx
import { useState,useCallback } from 'react';
export default function Child() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  const callback = useCallback(()=>{
    // 这里打印是旧值
    // 但是当a更新时,再次调用callback则是新的b值
    console.log("b in useCallback:", b);
    setB((val)=>{
      // 这里打印的是新值
      console.log("setB 的参数", val)
      return val + 1
    })
  },[a])
    const callback2 = useCallback(()=>{
    // 这里永远打印是旧值
    console.log("b in useCallback:", b);
    setB((val)=>{
      // 这里打印的是新值
      console.log("setB 的参数", val)
      return val + 1
    })
  },[])
  const callback3 = useCallback(()=>{
    // 这里永远打印是新值
    console.log("b in useCallback:", b);
    setB((val)=>{
      // 这里打印的是新值
      console.log("setB 的参数", val)
      return val + 1
    })
  },[b])
  return (
    <div>
      <button onClick={() => setA(a + 1)}>add a</button>
      <button onClick={() => setB(b + 1)}>add b</button>
      <button onClick={callback}>callback</button>
      <button onClick={callback2}>callback2</button>
      <button onClick={callback3}>callback3</button>
    </div>
  );
}

useContext

  • UseContext 基本使用
查看代码
jsx
import React, { useState, useContext,useCallback } from 'react';
const MyContext = React.createContext('没路用的初始值');

function MyComponent() {
  const [state, setState] = useState('state');
  const handleClick = () => {
    setState(val  => val + "1");
  };
  const [show, setShow] = useState(false)
  const showHandle = useCallback(()=>{
    setShow((status) => !status)
  },[])
  return (
    <MyContext.Provider value={{showHandle,state}}>
      <ul>
        <li>{show ? '显示':'隐藏'}</li>
        <li><button onClick={handleClick}>更新state</button></li>
        <MyChildComponent />
      </ul>
    </MyContext.Provider>
  );
}

function MyChildComponent() {
  return (
    <MyGrandchildComponent />
  );
}

function MyGrandchildComponent() {
  const {state,showHandle} = useContext(MyContext);
  // debugger
  return (
    <>
    <li>{state}</li>
    <li ><button onClick={showHandle}>更新show</button></li>
    </>
  );
}

export default MyComponent;
  • 弹窗提示 基于UseContext的实现
查看代码
tsx
'use client'
import React, {
  createContext,
  useContext,
  useState,
  useCallback,
  ReactNode,
} from "react";

// Alert 类型
type AlertType = "success" | "error" | "info" | "warning";

interface Alert {
  id: number;
  message: string;
  type: AlertType;
}

// Context 类型
interface AlertContextType {
  show: (message: string, type?: AlertType, duration?: number) => void;
}

const AlertContext = createContext<AlertContextType | undefined>(undefined);

interface AlertProviderProps {
  children: ReactNode;
}

export const AlertProvider: React.FC<AlertProviderProps> = ({ children }) => {
  const [alerts, setAlerts] = useState<Alert[]>([]);

  const show = useCallback(
    (message: string, type: AlertType = "info", duration: number = 3000) => {
      const id = Date.now(); // 唯一 id
      setAlerts((prev) => [...prev, { id, message, type }]);

      // 自动移除
      setTimeout(() => {
        setAlerts((prev) => prev.filter((a) => a.id !== id));
      }, duration);
    },
    []
  );

  const remove = useCallback((id: number) => {
    setAlerts((prev) => prev.filter((a) => a.id !== id));
  }, []);

  return (
    <>
    <h2>AlertProvider</h2>
    <AlertContext.Provider value={{ show }}>
      {children}
      <div className="fixed top-5 right-5 flex flex-col gap-2 z-50">
        {alerts.map((alert) => (
          <div
            key={alert.id}
            className={`alert ${
              alert.type === "success"
                ? "alert-success"
                : alert.type === "error"
                ? "alert-error"
                : alert.type === "warning"
                ? "alert-warning"
                : "alert-info"
            }`}
          >
            <span>{alert.message}</span>
            <button
              className="btn btn-sm btn-ghost ml-2"
              onClick={() => remove(alert.id)}
            >
              ×
            </button>
          </div>
        ))}
      </div>
    </AlertContext.Provider>
    </>
  );
};


export const useAlert = (): AlertContextType => {
  const context = useContext(AlertContext);
  if (!context) {
    throw new Error("useAlert 必须在 AlertProvider 内使用");
  }
  return context;
};

jsx


import {useAlert} from "./Alert";
export default function Child(){
  const {show} = useAlert()
  const test = ()=>{
    show("123","error")
  }
  return <button onClick={test}>alert</button>
}

forwardRef

  • React 19 以前,ref 并不能直接声明为一个组件的 prop,而是需要借助 forwardRef API
查看代码
jsx
import { forwardRef, useRef, useEffect } from 'react';

// React 19 以前,ref 并不能直接声明为一个组件的 prop,而是需要借助 forwardRef API
const NewCardInput = forwardRef(function NewCardInput (props, ref) {
  const { title, onChange, onKeyDown } = props;
  return (
    <input type="text" value={title} ref={ref}
      onChange={onChange} onKeyDown={onKeyDown} />
  );
})

export default function KanbanNewCard () {
  const inputElem = useRef(null);
  useEffect(() => {
    setTimeout(() => inputElem.current.focus(), 100);
  }, []);

  function handleChange () {
    console.log("change");
  }
  function handleKeyDown () {
    console.log("keydown");
  }

  return (
    <div>
      <h2>forwardRef</h2>
      <NewCardInput ref={inputElem} onChange={handleChange} onKeyDown={handleKeyDown} />
    </div>
  );
}

useMeno

  • 基本使用
查看代码
jsx
import { useMemo, useState, useEffect } from "react"
export default function Meno () {
  const [count, setCount] = useState(1)
  const [count2, setCount2] = useState(1)
  const memo = useMemo(() => {
    console.log('useMemo')
    return count + 10
  }, [count])
  useEffect(() => {
    console.log('useMemo中 useEffect')
  }, [count])
  return (
    <>
      <h2>useMemo</h2>
      <div>Meno</div>
      <div>count:{count}</div>
      <div>count2:{count2}</div>
      <div>memo:{memo}</div>
      <button onClick={() => setCount(count + 1)}>count++</button>
      <button onClick={() => setCount2(count2 + 1)}>count2++</button>
    </>
  )
  // 打印结果
  // 首次渲染:useMemo 组件渲染 useEffect
  // 点击 count++:useMemo 组件渲染 useEffect
  // 点击 count2++:组件渲染
}

React.Meno

  • 基本使用
查看代码
jsx
import React, { useState } from 'react';

// 普通子组件,每次父组件渲染都会被重新渲染
const Child = ({ count, id }) => {
  console.log('Child render', id);
  return <div>Child count: {count} id: {id}</div>;
};

// 使用 React.memo 包裹后,props 相同不重新渲染
const MemoizedChild = React.memo(Child);

function App () {
  const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(0);

  return (
    <div>
      <h2>react.memo</h2>
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      {/* 关键点:点击button,count2不会重新渲染MemoizedChild*/}
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
      <MemoizedChild count={count} id={1} />
      <Child count={count2} id={2} />
    </div>
  );
}

export default App;

Props

  • props各种传值
查看代码
jsx

function App() {
  const todoList = [
    { title: '开发任务-1', status: '22-05-22 18:15' },
    { title: '开发任务-2', status: '22-05-22 18:15' },
    { title: '开发任务-3', status: '22-05-22 18:15' },
  ];
  const KanbanCard = ({ title, status }) => {
    return (
      <li className="kanban-card">
        <span className="card-title">{title}&nbsp;&nbsp;&nbsp;&nbsp;</span>
        <span className="card-status">{status}</span>
      </li>
    );
  };
  const KanbanColumn = ({ title, children }) => {
    return (
      <section>
        {title}
        {children}
      </section>
    );
  };
  const handleAdd = () => {
    console.log('handleAdd');
  };
  const showAdd = false;
  const MyComponent = ({ prop1, prop2, booleanProp, onClick }) => (
    <div>
      <span>prop1文本:{prop1},  </span>&nbsp;
      <span>prop2数字:{prop2},</span>&nbsp;
      <span>booleanProp:{booleanProp ? 'true' : 'false'},</span>&nbsp;
      <button onClick={onClick}>Click me</button>
    </div>
  )
  return (<>
    <h2>Props</h2>
    <div className="App">
      {/* title 传递一个组件 */}
      <KanbanColumn title={
        <>
          待处理<button onClick={handleAdd}
            disabled={showAdd}>&#8853; 添加新卡片</button>
        </>
      }>
        <ul>
          {/* props 传递 一个对象*/}
          { todoList.map((props,index) => <KanbanCard {...props} key={index} />) }
        </ul>
      </KanbanColumn>
      {/* props 传递各种类型的值 */}
      <MyComponent prop1="我的" prop2={123} booleanProp={false}
      onClick={(evt) => {console.log('clicked',evt)}} ></MyComponent>
    </div></>
  );
}
export default App;

useRef

  • useRef基本使用
查看代码
jsx
import { useRef } from 'react';

function Test1() {
  const myRef = useRef(0);

  return (<div>
    <button onClick={() => {
      myRef.current++;
      // 不会触发组件重新渲染
      console.log(myRef.current);
    }}>设置</button>
  </div>);
}
function Test2() {
  // 通过 ref 操作 DOM
  const myRef = useRef(null);
  return (<div>
    <div ref={myRef}>111</div>
    <button onClick={() => {
      myRef.current.innerText = "222";
    }}>设置</button>
  </div>);
}
export default function App () {
  return (
    <>
      <h2>useRef</h2>
      <Test1></Test1>
      <Test2></Test2>
    </>
  );
};