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声明周期

类组件与函数组件对应

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

vue与react对应

vue react react
类组件 函数组件
watch 类组件的getDerivedStateFromProps useEffect,需要传递依赖项, Effect 在 初始渲染后以及依赖项变更的重新渲染后
computed get ‘属性名’

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 替代方案 核心区别
componentDidMount useEffect(() => {}, []) 依赖数组空表示仅挂载执行
componentDidUpdate useEffect(() => {}, [deps]) 依赖变化时触发
componentWillUnmount useEffect(() => { return fn }, []) 清理函数在卸载时执行
shouldComponentUpdate React.memo() + useMemo/useCallback 浅比较或自定义比较函数
getDerivedStateFromProps useState + 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

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

更多推荐