在 React 中,“单向数据流”(unidirectional data flow)是指数据在应用程序中的流动方向始终是单向的,即从上层组件(父组件)流向下层组件(子组件)。这种数据流动方式有助于保持数据的可预测性和一致性,便于调试和维护
以下是单向数据流的一些关键点:
- 状态提升(Lifting State Up):当多个组件需要共享某些状态时,将这些状态提升到它们的最近公共祖先组件中。这种方式确保数据源唯一,避免状态不一致
import React, { useState } from 'react';
// 公共祖先组件
function ParentComponent() {
// 定义共享状态
const [sharedState, setSharedState] = useState('initial state');
// 渲染子组件,并传递共享状态和更新函数
return (
<div>
<ChildComponentA sharedState={sharedState} setSharedState={setSharedState} />
<ChildComponentB sharedState={sharedState} setSharedState={setSharedState} />
</div>
);
}
// 子组件A
function ChildComponentA({ sharedState, setSharedState }) {
return (
<div>
<h1>Child Component A</h1>
<p>Shared State: {sharedState}</p>
<button onClick={() => setSharedState('updated by A')}>Update from A</button>
</div>
);
}
// 子组件B
function ChildComponentB({ sharedState, setSharedState }) {
return (
<div>
<h1>Child Component B</h1>
<p>Shared State: {sharedState}</p>
<button onClick={() => setSharedState('updated by B')}>Update from B</button>
</div>
);
}
function App() {
return <ParentComponent />;
}
export default App;
-
通过 props 传递数据:父组件通过 props 向子组件传递数据。子组件只能读取 props,而不能修改 props。这使得数据流动明确,数据来源明确
-
单一数据源:在 React 应用中,通常有一个单一的数据源(即状态),通过状态管理(如 Redux 或 Context API)来管理和更新应用中的状态
// 这是一个 React Context 的例子
import React, { createContext, useState, useContext } from 'react';
// 创建一个 Context 对象
const MyContext = createContext();
const MyProvider = ({ children }) => {
const [data, setData] = useState("Hello, World!");
const updateData = (newData) => {
setData(newData);
};
return (
<MyContext.Provider value={{ data, updateData }}>
{children}
</MyContext.Provider>
);
};
const ChildComponent = () => {
const { data, updateData } = useContext(MyContext);
const handleChange = (event) => {
updateData(event.target.value);
};
return (
<div>
<input type="text" value={data} onChange={handleChange} />
<p>{data}</p>
</div>
);
};
const App = () => {
return (
<MyProvider>
<ChildComponent />
</MyProvider>
);
};
export default App;
- 不可变数据:React 中提倡使用不可变数据结构。这意味着状态的更新总是返回一个新的状态对象,而不是直接修改现有的状态对象。这有助于避免副作用,提高应用的可预测性
// 更新 State 时,不直接修改它而是设置为一个全新的值
import React, { useState } from 'react';
function MyComponent() {
// 初始化状态
const [items, setItems] = useState([{ id: 1, value: 'Item 1' }, { id: 2, value: 'Item 2' }]);
// 添加新项目
const addItem = item => {
// 不直接修改items,而是返回一个新的数组
setItems(prevItems => [...prevItems, item]);
};
// 更新现有项目
const updateItem = (id, newValue) => {
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, value: newValue } : item
)
);
};
return (
<div>
<button onClick={() => addItem({ id: 3, value: 'Item 3' })}>Add Item 3</button>
{items.map(item => (
<div key={item.id}>
<span>{item.value}</span>
<button onClick={() => updateItem(item.id, 'Updated Value')}>Update</button>
</div>
))}
</div>
);
}
export default MyComponent;
// 也可以使用 immer 来实现不可以变数据
// 以下示例代码来自 immer 官网: https://immerjs.github.io/immer/zh-CN/example-setstate/
import React, { useCallback, useState } from "react";
import {produce} from "immer";
const TodoList = () => {
const [todos, setTodos] = useState([
{
id: "React",
title: "Learn React",
done: true
},
{
id: "Immer",
title: "Try Immer",
done: false
}
]);
const handleToggle = useCallback((id) => {
setTodos(
produce((draft) => {
const todo = draft.find((todo) => todo.id === id);
todo.done = !todo.done;
})
);
}, []);
const handleAdd = useCallback(() => {
setTodos(
produce((draft) => {
draft.push({
id: "todo_" + Math.random(),
title: "A new todo",
done: false
});
})
);
}, []);
return (<div>{*/ See CodeSandbox */}</div>)
}
这就是保证数据单向流动的几个关键点(共享状态提升到父级、使用 props 传递数据、单一数据源、不可变数据),通过这些规则或限制,可以让我们的代码的行为更加可预测,也就更好维护