calc 失效

      height: calc(~"100vh - 102px");
需要写成这种样式

Hooks只能在函数组件中使用

可以理解为听过Hooks 为函数组件钩入 class 组件的特性

Hooks 前后 , 组件开发模式的对比

  • React v16.8 以前: class组件(提供状态) + 函数组件(展示内容)
  • React v16.8 及其以后:
  1. class组件(提供状态) + 函数组件(展示内容)
  2. Hooks(提供状态) + 函数组件(展示内容)
  3. 混用以上两种方式: 部分功能用 class 组件 , 部分功能用 Hooks + 函数组件

📊 Hook vs 普通函数对比

特征React Hook普通函数
命名use 开头任意命名
状态管理可以使用 useState、useEffect无状态
组件生命周期可以访问组件生命周期无法访问
调用位置只能在 React 组件中调用(函数组件的最外层作用域调用)任何地方都可以调用
返回值可以返回状态、函数等返回普通数据

直接导入 STORE和通过 Context 获取 store区别

特性直接导入 STORE通过 Context 获取 store
响应式❌ 非响应式✅ 响应式
组件更新❌ 不会触发组件更新✅ 会触发组件更新
作用域✅ 全局单例✅ 组件作用域
测试性❌ 难以测试✅ 易于测试
性能✅ 性能更好❌ 可能影响性能
1. 响应式差异
直接导入 STORE(非响应式)
// ❌ 不会响应 store 状态变化
const commonState = STORE.getState(); // 获取当前快照
// 即使 store 状态变化,这个值也不会更新
通过 Context 获取(响应式)
// ✅ 响应 store 状态变化
let { store } = useContext(StoreContext);
const commonState = store.getState()?.common;
// 当 store 状态变化时,组件会重新渲染
2. 使用场景差异
适合直接导入 STORE 的场景
// ✅ 工具函数、纯计算逻辑
export const labelByValue = (key, value) => {
    const commonState = STORE.getState(); // 只需要当前状态快照
    return commonState?.common?.allOptions?.[key]?.find(t => t.value === value)?.label;
};

// ✅ 事件处理、非渲染逻辑
const handleClick = () => {
    const state = STORE.getState(); // 不需要响应式更新
    // 处理逻辑...
};
适合通过 Context 获取的场景
// ✅ React 组件、需要响应状态变化
const MyComponent = () => {
    let { store } = useContext(StoreContext); // 需要响应式更新
    const data = store.getState()?.data;
    
    return <div>{data}</div>; // 数据变化时自动更新
};

// ✅ 自定义 Hook
export const useLabelByValue = (key, value) => {
    let { store } = useContext(StoreContext);
    const label = store.getState()?.common?.allOptions?.[key]?.find(t => t.value === value)?.label;
    return label; // 返回响应式数据
};

store.subscribe作用

  • 就是store.subscribe如果直接写在函数组件里,那么该函数组件每渲染一次,就会给store增加一个“订阅”
  store.subscribe(() => {
    console.log(femaleSex,num,222222222);
    setRandom(Math.random());
  });

useEffect

store.subscribe写在useEffect中 store.subscribe()因为传递给useEffect的第二个参数为一个空数组,所以回调只执行了一次
useEffect的回调函数里面的基本类型变量永远拿的是useEffect回调第一次执行 函数组件的基本类型变量(因为基本类型变量的赋值是值传递)

  useEffect(() => {
    store.subscribe(() => {
      setRandom( v => {
        console.log(femaleSex,v,222222222);
        return Math.random()
      });
      // setFemaleSex(true)
      // setFemaleSex((v) => {
      //   return !v
      // })
    });
  }, []);//传递空数组 在初次渲染后才会执行 相当于只类组件的componentsDidMounted声明周期

类组件与函数组件对应

类组件函数组件
stateuseState
生命周期useEffect(依赖参数为[] = componentDidMount, 依赖项不写 = componentDidMount+ componentDidUpdate
ref / React.createRefuseRef

vue与react对应

vuereactreact
类组件函数组件
watch类组件的getDerivedStateFromPropsuseEffect,需要传递依赖项, Effect 在 初始渲染后以及依赖项变更的重新渲染后
computedget ‘属性名’

React中的flushSync与Vue中的nextTick

React中的flushSync与Vue中的nextTick是两种用于处理异步更新的机制。它们在React和Vue这两个流行的前端框架中起着重要的作用。

首先,让我们来看看flushSync。在React中,当需要更新UI时,React会将更新操作放入一个队列中,然后异步地执行这些更新操作。但是有时候,我们希望立即执行这些更新操作,而不是等待异步执行。这时就可以使用flushSync。flushSync会立即执行队列中的所有更新操作,确保更新是同步完成的。这对于某些特定的场景非常有用,比如在进行DOM测量之前,我们可能需要确保所有的更新已经完成。

而在Vue中,nextTick则是用来处理异步更新的机制。和React类似,当需要更新UI时,Vue也会将更新操作放入一个队列中,然后异步地执行这些更新操作。但是有时候,我们需要在DOM更新之后立即执行一些操作,比如获取更新后的DOM元素。这时就可以使用nextTick。nextTick会在DOM更新之后执行回调函数,确保我们可以在更新后立即访问到最新的DOM元素。

虽然flushSync和nextTick都是用于处理异步更新的机制,但它们的使用方式和场景略有不同。flushSync主要用于React中,用于确保更新是同步完成的;而nextTick则主要用于Vue中,用于在DOM更新之后执行回调函数。

总的来说,flushSync和nextTick都是非常有用的工具,它们可以帮助我们更好地处理异步更新,提升应用的性能和用户体验。无论是React还是Vue,都值得我们深入了解和使用。

传统生命周期(类组件)与 Hooks 对照表

类组件生命周期方法函数组件 Hooks 替代方案核心区别
componentDidMountuseEffect(() => {}, [])依赖数组空表示仅挂载执行
componentDidUpdateuseEffect(() => {}, [deps])依赖变化时触发
componentWillUnmountuseEffect(() => { return fn }, [])清理函数在卸载时执行
shouldComponentUpdateReact.memo() + useMemo/useCallback浅比较或自定义比较函数
getDerivedStateFromPropsuseState + useEffect更明确的派生状态管理
getSnapshotBeforeUpdate无直接等效,需要自定义 Hook罕见需求,通常可以重构避免

React Hooks 的设计更加强调"同步副作用到数据变化"的理念

状态(state)改变导致组件重新渲染

React 函数组件中的一个状态(state)改变,默认会导致整个函数组件重新执行(重新渲染),但这并不意味着一定会导致实际的DOM更新。

基本渲染行为
  1. 状态更新触发重新渲染

    • 当调用 setState 或使用 useReducer 的 dispatch 时
    • React 会安排一次新的渲染
    • 整个函数组件会从头到尾重新执行一次
    function Counter() {
      const [count, setCount] = useState(0); // 状态声明
      console.log('组件重新渲染'); // 每次状态变化都会打印
      
      return (
        <button onClick={() => setCount(c => c + 1)}>
          点击 {count} 次
        </button>
      );
    }
    
关键细节说明
  1. 渲染 ≠ DOM更新

    • React 会进行 虚拟DOM对比(reconciliation)
    • 只有发现真实DOM需要变化时才会更新
  2. 子组件的影响

    • 默认情况下,父组件重新渲染会导致所有子组件重新渲染
    • 除非子组件使用 React.memo 进行了优化
    // 子组件默认会随父组件一起重新渲染
    function Parent() {
      const [state, setState] = useState();
      return <Child />; // Child每次都会重新渲染
    }
    
性能优化方式
  1. React.memo (针对props不变的情况):

    const Child = React.memo(function Child({ text }) {
      console.log('只有props变化时才重新渲染');
      return <div>{text}</div>;
    });
    
  2. useMemo/useCallback (保持引用稳定):

    function Parent() {
      const [count, setCount] = useState(0);
      const memoizedChild = useMemo(() => <Child />, []);
      const stableFn = useCallback(() => {}, []);
      
      return <div>{memoizedChild}</div>;
    }
    
  3. 状态提升/下降 (隔离变化范围):

    // 将频繁变化的状态下放到更小组件
    function Parent() {
      return <Counter />; // 状态控制在Counter内部
    }
    
特殊情况说明
  1. 相同的state值

    • 如果新state值与当前state相同(Object.is比较)
    • React 会跳过渲染 (包括子组件)
    const [value, setValue] = useState(0);
    // 下面的调用不会导致重新渲染:
    setValue(0); 
    
  2. 批量更新 (React 18自动批处理):

    function handleClick() {
      setCount(c => c + 1);  // 不会立即触发渲染
      setName('new');       // 不会立即触发渲染
      // React会将这两个更新合并为一次渲染
    }
    
为什么这样设计?

React选择默认重新执行整个函数组件的优点是:

  1. 避免手动管理shouldComponentUpdate的复杂性
  2. 保证状态和渲染的一致性
  3. 现代JavaScript引擎执行函数很快
  4. 配合虚拟DOM Diff算法效率足够

useEffect导致的死循环

🎯 useEffect 依赖数组详解

依赖数组执行时机使用场景
[]只在组件挂载时执行一次初始化数据
[dep1, dep2]依赖变化时执行响应状态变化
无依赖数组每次渲染都执行几乎不用(会导致死循环)

使用 useEffect,依赖项传递是状态(useState)情况下,第一个回调函数参数是不是不能改变其他的状态,不然会导致死循环

    const [ggg, setGgg] = useState([]);
     const [aaa, setAaa] = useState([]);
    useEffect(() => {
        setGgg('ffff');
    }, [aaa])

组件渲染-useEffect回调执行-改变状态(ggg)-触发组件重新渲染
📋 其他可能的死循环场景
1. 错误的依赖项
// ❌ 错误:依赖了会变化的状态
useEffect(() => {
    setCount(count + 1); // 更新依赖的状态
}, [count]); // 依赖自身,死循环!

// ✅ 正确:使用函数式更新避免依赖
useEffect(() => {
    setCount(prev => prev + 1); // 不依赖当前值
}, []); // 空依赖数组
2. 对象/数组依赖
// ❌ 错误:对象每次渲染都是新的
useEffect(() => {
    fetchData(obj); // obj 每次渲染都是新对象
}, [obj]); // 死循环!

// ✅ 正确:使用基本类型或 useMemo
const objString = JSON.stringify(obj);
useEffect(() => {
    fetchData(JSON.parse(objString));
}, [objString]); // 字符串是稳定的
3. 函数依赖
// ❌ 错误:函数每次渲染都是新的
useEffect(() => {
    handleClick(); // 函数每次渲染都重新创建
}, [handleClick]); // 死循环!

// ✅ 正确:使用 useCallback
const handleClick = useCallback(() => {
    // 函数逻辑
}, []); // 稳定的函数引用

useEffect(() => {
    handleClick();
}, [handleClick]); // 不会死循环
避免死循环的5种正确方式
1. 添加条件判断
useEffect(() => {
  if (count < 10) { // 添加终止条件
    setCount(count + 1);
  }
}, [count]);
2. 使用函数式更新
useEffect(() => {
  setCount(prevCount => prevCount + 1); // ✅ 不直接依赖外部count
}, [/* 可省略count依赖 */]); 
3. 移除非必要依赖
const [data, setData] = useState(null);

useEffect(() => {
  fetch('/api').then(res => {
    // ✅ 不依赖data,只在mount时执行
    setData(res.data);
  });
}, []); // 空依赖数组
4. 拆分useEffect
const [filter, setFilter] = useState('');
const [results, setResults] = useState([]);

// 监听filter变化触发搜索
useEffect(() => {
  searchAPI(filter).then(setResults);
}, [filter]);

// 独立的状态更新
const handleChange = (value) => {
  setFilter(value);
};
5. 使用useReducer
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    default:
      return state;
  }
}

function Component() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  
  useEffect(() => {
    if (state.count < 5) {
      dispatch({ type: 'INCREMENT' }); // ✅ 更安全的更新方式
    }
  }, [state.count]);
  
  return <div>{state.count}</div>;
}
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐