面试官 🤔:如何理解 React 中的“单向数据流”……

812 阅读3分钟

Screen Recording 2024-07-06 at 23.10.35 (2).gif

在 React 中,“单向数据流”(unidirectional data flow)是指数据在应用程序中的流动方向始终是单向的,即从上层组件(父组件)流向下层组件(子组件)。这种数据流动方式有助于保持数据的可预测性和一致性,便于调试和维护

以下是单向数据流的一些关键点:

  1. 状态提升(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;
  1. 通过 props 传递数据:父组件通过 props 向子组件传递数据。子组件只能读取 props,而不能修改 props。这使得数据流动明确,数据来源明确

  2. 单一数据源:在 React 应用中,通常有一个单一的数据源(即状态),通过状态管理(如 ReduxContext 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;

  1. 不可变数据: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 传递数据、单一数据源、不可变数据),通过这些规则或限制,可以让我们的代码的行为更加可预测,也就更好维护