You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React.js集成Redux及解决subscribe重复执行问题

React.js集成Redux及subscribe重复执行问题解决

问题根源

你遇到的store.subscribe执行次数累加的问题,核心原因是每次App组件重新渲染时,都会调用一次store.subscribe添加新的订阅函数。状态更新会触发所有已注册的订阅,导致执行次数越来越多(第一次更新1次,第二次2次,以此类推)。

解决方案一:修复手动订阅逻辑

利用React的useEffect钩子,确保仅在组件挂载时订阅一次,并在组件卸载时取消订阅,避免重复注册:

修改App组件代码如下:

const App = ({store}) => {
    const [todos, setTodos] = React.useState([])
    const [goals, setGoals] = React.useState([])

    React.useEffect(() => {
        // 订阅store变化,返回取消订阅的函数
        const unsubscribe = store.subscribe(() => {
            const newState = store.getState()
            setTodos(newState.todos)
            setGoals(newState.goals)
        })

        // 组件卸载时取消订阅
        return unsubscribe
    }, [store]) // 依赖仅为store,确保只执行一次

    return (
        <div>
            <Todos items={todos} />
            <Goals items={goals} />
        </div>
    )
}

解决方案二:使用官方react-redux库(推荐)

手动订阅是Redux的基础用法,但在React项目中,官方推荐使用react-redux库简化集成,无需手动处理订阅、状态同步:

步骤1:引入react-redux库

在HTML的head中添加:

<script src="https://unpkg.com/react-redux@8.1.3/dist/react-redux.min.js"></script>

步骤2:用Provider包裹根组件

将store通过Provider注入整个应用,子组件可直接获取:

// Root
const root = ReactDOM.createRoot(document.getElementById("app"))

root.render(
    <ReactRedux.Provider store={store}>
        <App />
    </ReactRedux.Provider>
)

步骤3:用useSelector和useDispatch替代手动订阅和dispatch

修改App、Todos组件:

const App = () => {
    // 直接从store中选择所需状态
    const todos = ReactRedux.useSelector(state => state.todos)
    const goals = ReactRedux.useSelector(state => state.goals)

    return (
        <div>
            <Todos items={todos} />
            <Goals items={goals} />
        </div>
    )
}

const Todos = ({items}) => {
    const newTodoInput = React.useRef()
    // 获取dispatch函数
    const dispatch = ReactRedux.useDispatch()

    const handleAddTodo = () => {
        const newTodo = newTodoInput.current
        dispatch(
            addTodo({
                id: (new Date()).getTime().toString(36),
                text: newTodo.value,
                complete: false
            })
        )
        newTodo.value = ""
    }

    const handleRemoveTodo = (e) => {
        dispatch(removeTodo(e.target.id))
    }

    const handleToggle = (e) => {
        dispatch(toggleTodo(e.target.id))
    }

    return (
        <div>
            <h3>My Todos:</h3>
            <div>
                <input type="text" placeholder="new todo ..." ref={newTodoInput} />
                <button onClick={handleAddTodo}>Save</button>
            </div>
            <List items={items} removeItem={handleRemoveTodo} toggle={handleToggle} />
        </div>
    )
}

完整修改后的代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React + Redux Demo</title>

    <!-- Redux -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.min.js" integrity="sha512-1/8Tj23BRrWnKZXeBruk6wTnsMJbi/lJsk9bsRgVwb6j5q39n0A00gFjbCTaDo5l5XrPVv4DZXftrJExhRF/Ug==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <!-- react & react-dom -->
    <script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
    <!-- react-redux -->
    <script src="https://unpkg.com/react-redux@8.1.3/dist/react-redux.min.js"></script>
    <!-- Babel -->
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
    <div id="app"></div>

    <script type="text/babel">
        // Redux部分保持不变
        const [ADD_TODO, REMOVE_TODO, TOGGLE_TODO] = ["ADD_TODO", "REMOVE_TODO", "TOGGLE_TODO"]
        const [ADD_GOAL] = ["ADD_GOAL"]

        const addTodo = todo => ({type: ADD_TODO, todo})
        const removeTodo = id => ({type: REMOVE_TODO, id})
        const toggleTodo = id => ({type: TOGGLE_TODO, id})
        const addGoal = goal => ({type: ADD_GOAL, goal})

        const todosReducer = (state=[], action) => {
            switch (action.type) {
                case ADD_TODO:
                    return [...state, action.todo]
                case REMOVE_TODO:
                    return state.filter(s => s.id !== action.id)
                case TOGGLE_TODO:
                    return state.map(s => s.id === action.id ? {...s, complete: !s.complete} : s)
                default:
                    return state
            }
        }

        const goalsReducer = (state=[], action) => {
            switch (action.type) {
                case ADD_GOAL:
                    return [...state, action.goal]
                default:
                    return state
            }
        }

        const store = Redux.createStore(
            Redux.combineReducers({todos: todosReducer, goals: goalsReducer})
        )

        // React组件部分
        const List = ({items, removeItem, toggle}) => {
            return (
                <ul>
                    {items.map(itm => (
                        <li key={itm.id}>
                            <span 
                                id={itm.id} 
                                onClick={toggle || null} 
                                style={{textDecoration: itm.complete ? 'line-through' : ''}}
                            >
                                {itm.text}
                            </span>
                            {removeItem && <button id={itm.id} onClick={removeItem}> X </button>}
                        </li>
                    ))}
                </ul>
            )
        }

        const Todos = ({items}) => {
            const newTodoInput = React.useRef()
            const dispatch = ReactRedux.useDispatch()

            const handleAddTodo = () => {
                const newTodo = newTodoInput.current
                if (!newTodo.value.trim()) return
                dispatch(addTodo({
                    id: (new Date()).getTime().toString(36),
                    text: newTodo.value,
                    complete: false
                }))
                newTodo.value = ""
            }

            const handleRemoveTodo = (e) => {
                dispatch(removeTodo(e.target.id))
            }

            const handleToggle = (e) => {
                dispatch(toggleTodo(e.target.id))
            }

            return (
                <div>
                    <h3>My Todos:</h3>
                    <div>
                        <input type="text" placeholder="new todo ..." ref={newTodoInput} />
                        <button onClick={handleAddTodo}>Save</button>
                    </div>
                    <List items={items} removeItem={handleRemoveTodo} toggle={handleToggle} />
                </div>
            )
        }

        const Goals = ({items}) => {
            return (
                <div>
                    <h3>My Goals:</h3>
                    <List items={items} />
                </div>
            )
        }

        const App = () => {
            const todos = ReactRedux.useSelector(state => state.todos)
            const goals = ReactRedux.useSelector(state => state.goals)

            return (
                <div>
                    <Todos items={todos} />
                    <Goals items={goals} />
                </div>
            )
        }

        const root = ReactDOM.createRoot(document.getElementById("app"))
        root.render(
            <ReactRedux.Provider store={store}>
                <App />
            </ReactRedux.Provider>
        )
    </script>
</body>
</html>

关键说明

  • 手动订阅时,useEffect的依赖数组设为[store],确保仅在组件挂载时订阅一次,返回的unsubscribe函数会在组件卸载时执行,清理订阅。
  • 使用react-redux时,Provider将store注入上下文,useSelector自动监听状态变化,仅在所选状态更新时触发组件重渲染,useDispatch直接获取dispatch函数,无需手动传递store。

内容的提问来源于stack exchange,提问作者osomaher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 10:24:11