React自定义Hook疑问:为何仅Test组件触发重渲染?
问题原因分析
核心点:React自定义Hook是复用状态逻辑的工具,而非共享状态的容器。每个组件调用自定义Hook时,Hook内部的useState都会生成完全独立的状态实例,不同组件的状态相互隔离。
- 当
Test组件调用useTest()时,Hook里的const [test, setTest] = useState(...)会初始化一套属于Test的专属状态; - 当
Test2组件调用useTest()时,同样会初始化另一套完全独立的test状态; - 你点击
Test的按钮,更新的只是Test组件自己那套test状态,Test2的test还是初始的["orange", "apple", "banana"],没有发生变化,所以Test2不会触发重渲染。
实现组件间共享状态的方案
如果想让Test和Test2共享同一套test状态,有两种常用实现方式:
1. 状态提升到共同父组件(App)
把状态和逻辑移到App组件,通过props传递给子组件:
// App.tsx import React, { useState } from 'react'; import { Test } from './Test'; import { Test2 } from './Test2'; function App() { const [test, setTest] = useState(["orange", "apple", "banana"]); const [count, setCount] = useState(0); const testSet = new Set(test); const increment = () => { setCount(count + 1); setTest(pre => [...pre, `grape${count}`]); }; return ( <> <Test test={test} testSet={testSet} increment={increment} /> <Test2 test={test} testSet={testSet} /> </> ); } export default App;
修改子组件接收props:
// Test.tsx export const Test = ({ test, testSet, increment }) => { console.log("Test1 is rendered"); return ( <div> <h1>Test1</h1> <h2>{test.join(', ')}</h2> <h2>{Array.from(testSet).join(', ')}</h2> <button onClick={increment}>Click me</button> </div> ); }
// Test2.tsx export const Test2 = ({ test }) => { console.log("Test2 is rendered"); return ( <div> <h1>Test2</h1> <h2>{test.join(', ')}</h2> </div> ); }
2. 使用Context API(适合跨多层组件共享)
如果组件层级较深,props传递过于繁琐,可以用Context统一管理共享状态:
首先创建Context和Provider:
// hooks/TestContext.tsx import React, { createContext, useContext, useState, useMemo } from 'react'; interface TestContextType { test: string[]; testSet: Set<string>; increment: () => void; } const TestContext = createContext<TestContextType | undefined>(undefined); export const TestProvider = ({ children }: { children: React.ReactNode }) => { const [test, setTest] = useState(["orange", "apple", "banana"]); const [count, setCount] = useState(0); // 用useMemo避免每次渲染都创建新Set const testSet = useMemo(() => new Set(test), [test]); const increment = () => { setCount(count + 1); setTest(pre => [...pre, `grape${count}`]); }; const value = useMemo(() => ({ test, testSet, increment }), [test, testSet, increment]); return <TestContext.Provider value={value}>{children}</TestContext.Provider>; }; export const useTestContext = () => { const context = useContext(TestContext); if (!context) { throw new Error('useTestContext must be used within a TestProvider'); } return context; };
在App中包裹Provider:
// App.tsx import React from 'react'; import { Test } from './Test'; import { Test2 } from './Test2'; import { TestProvider } from './hooks/TestContext'; function App() { return ( <TestProvider> <Test /> <Test2 /> </TestProvider> ); } export default App;
子组件通过Context Hook获取状态:
// Test.tsx import { useTestContext } from "./hooks/TestContext"; export const Test = () => { console.log("Test1 is rendered"); const { test, testSet, increment } = useTestContext(); return ( <div> <h1>Test1</h1> <h2>{test.join(', ')}</h2> <h2>{Array.from(testSet).join(', ')}</h2> <button onClick={increment}>Click me</button> </div> ); }
// Test2.tsx import { useTestContext } from "./hooks/TestContext"; export const Test2 = () => { console.log("Test2 is rendered"); const { test } = useTestContext(); return ( <div> <h1>Test2</h1> <h2>{test.join(', ')}</h2> </div> ); }
修改后,点击Test组件的按钮,两个组件都会因共享状态更新触发重渲染。
内容的提问来源于stack exchange,提问作者Nanano
相关产品推荐
相关产品推荐

