1.React组件间如何通信? #

1.1 父组件向子组件通信 #

在 React 中,父组件向子组件传递数据是通过使用 props(属性)来实现的。props 是父组件传递给子组件的数据。子组件可以通过 this.props 来访问这些数据。

下面是一个简单的示例:

import React from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
// 创建子组件
class ChildComponent extends React.Component {
    render() {
      return (
        <div>
          <p>I am a child component</p>
          <p>My parent says: {this.props.messageFromParent}</p>
        </div>
      );
    }
  }

  // 创建父组件
  class ParentComponent extends React.Component {
    render() {
      return (
        <div>
          <p>I am a parent component</p>
          {/* 父组件通过props向子组件传递数据 */}
          <ChildComponent messageFromParent='Hello, child!' />
        </div>
      );
    }
  }
root.render(<ParentComponent/>);

在这个示例中:

  1. ParentComponent 是父组件,ChildComponent 是子组件。
  2. 父组件通过 <ChildComponent messageFromParent='Hello, child!' />messageFromParent 这个 prop 传递给子组件。这个 prop 的值是 'Hello, child!'
  3. 子组件可以通过 this.props.messageFromParent 来访问这个 prop。

1.2 子组件向父组件传递数据 #

在 React 中,子组件向父组件传递数据通常是通过回调函数实现的。父组件向子组件传递一个函数作为 prop,然后子组件在适当的时候调用这个函数,并传递数据给它。

以下是一个简单的示例:

import React,{useState} from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
// 子组件
function ChildComponent(props) {
    return (
      <div>
        <p>I am a child component</p>
        <button onClick={() => props.sendMessageToParent('Hello, parent!')}>
          Send Message to Parent
        </button>
      </div>
    );
  }
  // 父组件
  function ParentComponent() {
    const [messageFromChild, setMessageFromChild] = useState('');
    function handleMessageFromChild(message) {
      setMessageFromChild(message);
    }
    return (
      <div>
        <p>I am a parent component</p>
        <ChildComponent sendMessageToParent={handleMessageFromChild} />
        <p>Message from child: {messageFromChild}</p>
      </div>
    );
  }
root.render(<ParentComponent/>);

在这个示例中:

  1. ParentComponent 是父组件,ChildComponent 是子组件。
  2. 父组件定义了一个 handleMessageFromChild 函数,这个函数接收一个消息并将其设置为父组件的状态。然后,父组件将这个函数作为 sendMessageToParent 这个 prop 传递给子组件。
  3. 子组件包含一个按钮,当这个按钮被点击时,sendMessageToParent 这个 prop(即 handleMessageFromChild 函数)会被调用,并传递一个消息给它。
  4. 父组件接收到子组件传递的消息,并将其显示在页面上。

1.3 两个兄弟组件之间如何传递数据 #

在React中,兄弟组件之间不能直接通信。他们必须通过他们的共同父组件进行通信。一个兄弟组件将其数据传递给父组件,然后父组件将数据传递给另一个兄弟组件。

以下是一个简单的示例:

import React,{useState} from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
// 创建第一个子组件
function FirstChildComponent(props) {
    return (
      <div>
        <p>I am the first child component</p>
        <button onClick={() => props.sendMessageToParent('Hello, sibling!')}>
          Send Message to Sibling
        </button>
      </div>
    );
  }
  // 创建第二个子组件
  function SecondChildComponent(props) {
    return (
      <div>
        <p>I am the second child component</p>
        <p>Message from sibling: {props.messageFromSibling}</p>
      </div>
    );
  }
  // 创建父组件
  function ParentComponent() {
    const [message, setMessage] = useState('');
    function handleMessage(message) {
      setMessage(message);
    }
    return (
      <div>
        <p>I am the parent component</p>
        <FirstChildComponent sendMessageToParent={handleMessage} />
        <SecondChildComponent messageFromSibling={message} />
      </div>
    );
  }
root.render(<ParentComponent/>);

在这个示例中:

  1. ParentComponent 是父组件,FirstChildComponentSecondChildComponent 是子组件。
  2. 第一个子组件包含一个按钮,当这个按钮被点击时,它会调用 sendMessageToParent 这个 prop(即 handleMessage 函数),并传递一个消息给它。
  3. 父组件接收到第一个子组件传递的消息,并将其设置为自己的状态。然后,它将这个状态作为 messageFromSibling 这个 prop 传递给第二个子组件。
  4. 第二个子组件接收到 messageFromSibling 这个 prop,并将其显示在页面上。

1.4 Context API跨层级传递数据 #

React 的 Context API 可以用于在组件树中跨层级传递数据,这非常适合实现多语言功能,因为它可以让你在根组件处设置当前的语言,然后所有的子组件都能访问到这个语言设置,而不用一层层的传递。

以下是一个简单的示例:

import React, { useState,useContext,createContext } from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
// 创建一个 Context
const LanguageContext = createContext();
// 创建一个使用 Context 的子组件
function TextComponent() {
    const language = useContext(LanguageContext);
    const text = language === 'en' ? 'Hello, world!' : '你好,世界!';
    return <p>{text}</p>;
}
// 创建一个包含子组件的组件
function App() {
    const [language, setLanguage] = useState('en');
    function handleChangeLanguage(event) {
        setLanguage(event.target.value);
    }
    return (
        <LanguageContext.Provider value={language}>
            <select value={language} onChange={handleChangeLanguage}>
                <option value="en">English</option>
                <option value="zh">中文</option>
            </select>
            <TextComponent />
        </LanguageContext.Provider>
    );
}
root.render(<App />);

在这个示例中:

  1. 我们首先通过 createContext 创建了一个 LanguageContext
  2. App 组件是一个包含 TextComponent 的组件。它有一个 language 状态和一个 handleChangeLanguage 函数来改变这个状态。
  3. App 组件使用 LanguageContext.Provider 组件并传递 language 状态作为 value prop。这样,language 就可以在 LanguageContext 的所有消费者(即子组件)中使用了。
  4. TextComponent 是一个消费 LanguageContext 的组件。它使用 useContext 钩子来获取 language 的值,并根据这个值显示不同的文本。
  5. App 组件还包含一个下拉菜单,用于改变 language 的值。

1.5 通过全局变量和事件进行通信 #

在React中,全局变量和事件可以用于实现数据上报。一种常见的方法是在全局对象(例如,window 对象)上挂载临时数据和事件处理函数。

以下是一个简单的示例:

import React, { useEffect } from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root'));
window.global = {
  data: {},
  events: {}
};
function ReportDataComponent() {
  function handleButtonClick() {
    const dataToReport = {
      clicked: true
    };
    window.global.data = dataToReport;
    window.dispatchEvent(new CustomEvent('reportData', {
      detail: dataToReport
    }));
  }
  return <button onClick={handleButtonClick}>Click me</button>;
}
function HandleReportComponent() {
  useEffect(() => {
    window.addEventListener('reportData', handleReportData);
    return () => {
      window.removeEventListener('reportData', handleReportData);
    };
  }, []);
  function handleReportData(event) {
    console.log('Reported data:', event.detail);
  }
  return <div>Check the console after clicking the button</div>;
}
function App() {
  return <div>
            <ReportDataComponent />
            <HandleReportComponent />
        </div>;
}
root.render(<App />);

在这个示例中:

  1. 我们首先在 window 对象上创建了一个 global 对象,用于存储临时数据和事件处理函数。
  2. App 组件是一个简单的组件,包含一个按钮。
  3. App 组件挂载时,我们添加了一个全局事件 reportData。当这个事件被触发时,我们会调用 handleReportData 函数来处理数据上报的逻辑。
  4. 当按钮被点击时,我们会上报一些数据。我们将数据存储在 window.global.data 中,并触发 reportData 事件。
  5. handleReportData 函数会接收到 reportData 事件,并处理数据上报的逻辑。在这个示例中,我们只是将数据打印到控制台。

2.React状态管理框架 #

2.1 redux #

Redux: 英文文档 是一个用于管理应用程序状态的库。它通常与 React 一起使用,但实际上并不依赖于 React。Redux 帮助你管理应用程序的全局状态,使得可以跨组件共享状态。下面是 Redux 的工作原理:

React-Redux: 英文文档

  1. 动作 (Actions):这些是表示 UI 中发生了什么事情的普通 JavaScript 对象。它们必须具有一个 type 属性,也可以包含其他附加数据。例如:{ type: 'ADD_TODO', text: 'Buy milk' }

  2. Reducer:一个接受当前的 state 和一个 action,然后返回新的 state 的函数。重要的是,reducer 必须是纯函数;它们不修改输入状态,而是返回一个新的状态。例如:

    function todoReducer(state = initialState, action) {
      switch (action.type) {
        case 'ADD_TODO':
          return [...state, { text: action.text, completed: false }];
        default:
          return state;
      }
    }
    
  3. Store:存储是一个保存应用程序状态树的对象。在 Redux 应用程序中应该只有一个单一的存储。该存储具有一些简单的方法,如 dispatchgetStatesubscribe

  4. dispatch(action) 是最常用的。它用于将动作发送到存储。

  5. getState() 返回应用程序的当前状态。
  6. subscribe(listener) 注册一个在状态变化时调用的函数。

这是创建存储并分派一个动作的简单示例:

import { createStore } from 'redux';

const store = createStore(todoReducer);

store.dispatch({ type: 'ADD_TODO', text: 'Read a bit of the book' });

React-Redux 是一个提供绑定以将 Redux 与 React 一起使用的库。它提供了 Provider 组件和 connect 函数。

  1. ProviderProvider 组件使得 Redux 存储可供任何包装在 connect 函数中的嵌套组件使用。

    import { Provider } from 'react-redux';
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    
  2. connectconnect 函数将一个 React 组件连接到 Redux 存储。它不会修改传递给它的组件类;相反,它会返回一个新的,已连接的组件类供你使用。connect 函数接受两个参数,都是可选的:

  3. mapStateToProps:此函数用于从存储中选择连接的组件需要的数据的一部分。每次存储状态更改时都会调用它。它接收整个存储状态,并应返回组件需要的数据的对象。

  4. mapDispatchToProps:此参数可以是一个函数或一个对象。如果它是一个函数,它将作为 dispatch 传递给你的组件。如果它是一个对象,则假定其中的每个函数都是一个 Redux 动作创建器。一个具有相同函数名称的对象,但绑定到 Redux 存储中,将合并到组件的属性中。

     const mapStateToProps = (state) => ({
       todos: state.todos,
     });
    
     const mapDispatchToProps = {
       toggleTodo,
     };
    
     export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
    

将 Redux 和 React-Redux 一起使用,可以以可预测的方式使用动作、reducers 和中央存储来管理应用程序的全局状态并在组件之间共享。这种设置还使得更容易测试和调试你的应用程序。

2.2 toolkit #

@reduxjs/toolkit: 英文文档 是 Redux 的官方工具集,它简化了 Redux 的使用,以减轻 Redux 的学习曲线和降低维护成本。这个库提供了许多实用的工具,使你能够更方便、更简洁地写 Redux 代码。

Redux Toolkit 包含以下几个部分:

  1. createSlice: 这是 Redux Toolkit 中最重要的函数之一。它接受一个初始状态、一组 reducer 函数,和一个 slice 名字,然后自动生成 action creators 和 action types。这大大减少了我们需要手动编写的代码量。

  2. createAction: 这个函数用于创建 action creator 函数。一个 action creator 是一个返回具有 typepayload 属性的对象的函数。

  3. createReducer: 这个函数允许你指定一个初始状态和一个处理函数的对象,它会返回一个新的 reducer 函数。

  4. configureStore: 这个函数用于设置 store,它将多个 reducer 合并成一个 root reducer,并添加一些中间件,比如 Redux DevTools。

  5. createAsyncThunk: 这是 Redux Toolkit 中一个非常有用的工具,它允许你方便地处理异步逻辑。这个函数接受一个 action type 和一个返回 promise 的函数,并返回一个 thunk action creator。

以下是一个简单的例子,展示了如何使用 @reduxjs/toolkit

import { createSlice, configureStore } from '@reduxjs/toolkit'

// 使用 createSlice 创建一个 slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: state => state + 1,
    decrement: state => state - 1
  }
})

// 提取 action creators 和 reducer
const { actions, reducer } = counterSlice
export const { increment, decrement } = actions

// 创建 store
const store = configureStore({
  reducer: {
    counter: reducer
  }
})

export default store;

在上面的例子中,我们创建了一个名为 counter 的 slice,它有两个 action:incrementdecrementcreateSlice 函数自动为我们生成了对应的 action creators 和 action types。然后我们使用 configureStore 函数创建了 store,并将 counter reducer 添加到了 store 中。

以下是一个简单的 TodoApp 的示例。这个示例使用了 React, Redux, 和 React-Redux,并使用了 Redux Toolkit 来简化 Redux 的使用。在这个示例中,TodoApp 组件是应用程序的主要组件,它包含 AddTodoTodoList 组件。AddTodo 组件包含一个表单来添加新的待办事项,TodoList 组件包含一个列表的 TodoItem 组件,每个 TodoItem 组件显示一个待办事项的文本和一个复选框来标记待办事项为完成或未完成。

store.js 文件中,我们使用 configureStore 函数创建了 Redux 存储,并使用 todoReducer 作为根 reducer。在 todoSlice.js 文件中,我们使用 createSlice 函数创建了一个包含初始状态、reducer 和 action 的 slice。

App.js 文件中,我们使用 useSelector hook 从 Redux 存储中选择 todos 状态,然后传递给 TodoList 组件。我们还使用 useDispatch hook 创建一个 dispatch 函数,然后将它传递给 handleAddTodohandleToggleTodo 函数,这些函数分别用于添加新的待办事项和切换待办事项的完成状态。

AddTodo 组件包含一个表单,用户可以输入待办事项的文本,然后点击“添加”按钮来添加新的待办事项。当表

单提交时,它会调用 onAdd 函数,并将文本作为参数传递。

TodoList 组件接受一个 todos 数组和一个 onToggle 函数作为 props。它遍历 todos 数组并为每个待办事项创建一个 TodoItem 组件。每个 TodoItem 组件接受一个 todo 对象和一个 onToggle 函数作为 props。

TodoItem 组件显示待办事项的文本,并包含一个复选框,用户可以切换待办事项的完成状态。当复选框的状态改变时,它会调用 onToggle 函数,并将 todo.id 作为参数传递。

2.2.1 src/index.js #

src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import ReduxApp from './ReduxApp';
ReactDOM.createRoot(document.getElementById('root')).render(
  <ReduxApp />
);

2.2.2 store.js #

src\redux\store.js

// 从 '@reduxjs/toolkit' 导入 configureStore 方法。
import { configureStore } from '@reduxjs/toolkit';
// 从 './reducers/todoSlice' 导入 todoReducer。
import todoReducer from './todoSlice';
// 使用 configureStore 方法创建 store,并将 todoReducer 作为 todos 的 reducer。
export const store = configureStore({
    reducer: {
        todos: todoReducer,
    },
});

2.2.3 todoSlice.js #

src\redux\todoSlice.js

// 引入 @reduxjs/toolkit 包中的 createSlice 方法
import { createSlice } from '@reduxjs/toolkit';
// 定义初始状态,包含两个待办事项对象
const initialState = [
  { id: '1', text: 'Learn React', completed: false },
  { id: '2', text: 'Learn Redux', completed: false },
];
// 使用 createSlice 方法创建一个名为 'todos' 的 slice
const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    // 定义一个 addTodo 的 reducer,接收 action.payload 中的 id 和 text,
    // 并向 state 中添加一个新的待办事项
    addTodo: (state, action) => {
      const { id, text } = action.payload;
      state.push({ id, text, completed: false });
    },
    // 定义一个 toggleTodo 的 reducer,接收 action.payload 中的 id,
    // 并更改与 id 匹配的待办事项的 completed 状态
    toggleTodo: (state, action) => {
      const todo = state.find((todo) => todo.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
  },
});
// 导出 todosSlice.actions 对象,包含 addTodo 和 toggleTodo action creators
export const { addTodo, toggleTodo } = todosSlice.actions;
// 导出 todosSlice.reducer 作为默认导出,以便在配置 store 时使用
export default todosSlice.reducer;

2.2.4 ReduxApp.js #

src\ReduxApp.js

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo } from './redux/todoSlice';
function TodoApp() {
    const todos = useSelector((state) => state.todos);
    const dispatch = useDispatch();
    const handleAddTodo = (text) => {
        const id = todos.length + 1;
        dispatch(addTodo({ id, text }));
    };
    const handleToggleTodo = (id) => {
        dispatch(toggleTodo(id));
    };
    return (
        <div>
            <h1>Todo List</h1>
            <AddTodo onAdd={handleAddTodo} />
            <TodoList todos={todos} onToggle={handleToggleTodo} />
        </div>
    );
}
function AddTodo({ onAdd }) {
    const [text, setText] = React.useState('');
    const handleChange = (event) => {
        setText(event.target.value);
    };
    const handleSubmit = (event) => {
        event.preventDefault();
        onAdd(text);
        setText('');
    };
    return (
        <form onSubmit={handleSubmit}>
            <input type="text" value={text} onChange={handleChange} />
            <button type="submit">添加</button>
        </form>
    );
}
function TodoList({ todos, onToggle }) {
    return (
        <ul>
            {todos.map((todo) => (
                <TodoItem key={todo.id} todo={todo} onToggle={onToggle} />
            ))}
        </ul>
    );
}
function TodoItem({ todo, onToggle }) {
    return (
        <li>
            <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => onToggle(todo.id)}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
                {todo.text}
            </span>
        </li>
    );
}
export default TodoApp;

2.2 mobx #

MobX: 英文文档 是一个简单、可扩展的状态管理库,它通过反应性编程(reactive programming)原理使状态管理变得非常简单和直观。

下面是 MobX 的一些核心概念:

  1. Observable State(可观察状态): MobX 允许你创建可观察的状态。你可以将对象、数组、类实例甚至是普通变量标记为可观察的。

  2. Observer(观察者): 这是一个可以自动响应状态变化的函数。通常,这是一个 React 组件,但也可以是任何能够响应状态变化的函数。

  3. Actions(动作): 这是会修改状态的方法。在 MobX 中,你只能通过动作来修改状态。

  4. Computed Values(计算值): 这是由当前状态派生的值。当状态发生变化时,所有依赖该状态的计算值都会自动更新。

  5. Reaction(反应): 这是一种特殊的函数,它不会产生新的值,但会产生一些副作用,比如打印到控制台、发起网络请求等。

    如何使用MobX

  6. 安装 MobX: 你可以使用 npm 或者 yarn 来安装 MobX。

    npm install mobx --save
    
  7. 创建可观察的状态: 使用 observable 函数或 @observable 装饰器将你的状态标记为可观察的。

    import { observable } from 'mobx';
    
    const todo = observable({
      title: 'Learn MobX',
      completed: false,
    });
    
  8. 创建观察者: 使用 observer 函数或 @observer 装饰器将你的 React 组件标记为观察者。

    import { observer } from 'mobx-react-lite';
    
    const TodoView = observer(({ todo }) => <div>{todo.title}</div>);
    
  9. 修改状态: 使用 action 函数或 @action 装饰器将你的方法标记为动作。

    import { action } from 'mobx';
    
    const toggleCompleted = action(() => {
      todo.completed = !todo.completed;
    });
    
  10. 使用计算值: 使用 computed 函数或 @computed 装饰器将你的方法标记为计算值。

    import { computed } from 'mobx';
    
    const uppercaseTitle = computed(() => todo.title.toUpperCase());
    
  11. 使用反应: 使用 reactionautorun 函数创建反应。

    import { reaction } from 'mobx';
    
    reaction(
      () => todo.completed,
      completed => console.log(`Todo completed: ${completed}`)
    );
    

MobX 通过反应性系统自动跟踪状态变化,并在需要时更新观察者。这意味着你不需要担心组件何时或如何更新,MobX 会为你处理这些事情。

注意: MobX 6+ 版本推荐使用 makeObservablemakeAutoObservable 方法来创建 observable、action 和 computed,而不是使用 @observable@action@computed 装饰器。这是因为 JavaScript 装饰器仍然是一个实验性的特性。

2.2.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import MobxApp from './MobxApp';
ReactDOM.createRoot(document.getElementById('root')).render(
  <MobxApp />
);

2.2.2 store.js #

src\mobx\store.js

// 导入 mobx 的 makeAutoObservable 函数
import { makeAutoObservable } from "mobx"
// 创建 TodoStore 类
class TodoStore {
    // 初始化 todos 列表
    todos = [
        { id: 1, text: 'Learn React', completed: false },
        { id: 2, text: 'Learn MobX', completed: false }
    ]
    // 构造函数
    constructor() {
        // 将当前对象转化为 observable 对象
        makeAutoObservable(this)
    }
    // 添加 todo 的方法
    addTodo(text) {
        this.todos.push({ id: Math.random(), text, completed: false })
    }
    // 切换 todo 完成状态的方法
    toggleTodo(id) {
        const todo = this.todos.find(todo => todo.id === id)
        if (todo) todo.completed = !todo.completed
    }
    // 计算已完成 todo 数量的 getter 方法
    get completedTodosCount() {
        return this.todos.filter(todo => todo.completed).length
    }
}
// 创建 TodoStore 实例
const todoStore = new TodoStore()
// 导出 TodoStore 实例
export default todoStore

2.2.3 MobxApp.js #

src\MobxApp.js

import React, { useState } from 'react';
import todoStore from './mobx/store';
import { observer } from 'mobx-react';
const TodoList = observer(() => {
  const [newTodo, setNewTodo] = useState('');
  const handleNewTodoChange = (e) => {
    setNewTodo(e.target.value);
  };
  const handleAddTodo = () => {
    todoStore.addTodo(newTodo);
    setNewTodo('');
  };
  return (
    <div>
      <h1>Todos</h1>
      <input 
        type="text"
        value={newTodo}
        onChange={handleNewTodoChange}
      />
      <button onClick={handleAddTodo}>
        Add Todo
      </button>
      <ul>
        {todoStore.todos.map(todo => (
          <li key={todo.id}>
            <input 
              type="checkbox"
              checked={todo.completed}
              onChange={() => todoStore.toggleTodo(todo.id)}
            />
            {todo.text}
          </li>
        ))}
      </ul>
      <p>Completed Todos: {todoStore.completedTodosCount}</p>
    </div>
  );
});
export default TodoList;

2.3 recoil #

Recoil: 英文文档 是 Facebook 开发的一种新的状态管理库,它提供了一种灵活、高效的方式来管理 React 应用的状态。Recoil 通过 atoms 和 selectors 来管理和共享状态。

  1. Atoms: Atoms 是 Recoil 的基本单位。它们是可以被多个组件订阅的状态片段。当一个 atom 被更新时,所有订阅了该 atom 的组件将被重新渲染。

  2. Selectors: Selectors 用于根据 atoms 或其他 selectors 计算派生数据。可以将它们视为 pure functions,它们接受 atoms 或其他 selectors 作为输入,并返回一个新的值。

在下面示例中,我们首先导入了我们刚刚创建的 todoListState atom 和 filteredTodoListState selector。

然后,我们使用 useRecoilStateuseRecoilValue hooks 分别订阅了 todoListStatefilteredTodoListState

addTodo 函数用于向 todo 列表中添加一个新的 todo。toggleTodo 函数用于切换指定索引的 todo 的完成状态。

在组件的 return 语句中,我们显示了一个输入框、一个添加按钮和一个 todo 列表。当用户点击添加按钮时,将调用 addTodo 函数。当用户点击一个 todo 时,将调用 toggleTodo 函数。

2.3.1 index.js #

import React from 'react';
import ReactDOM from 'react-dom/client';
import RecoilApp from './RecoilApp';
import {RecoilRoot} from 'recoil';
ReactDOM.createRoot(document.getElementById('root')).render(
 <RecoilRoot><RecoilApp/></RecoilRoot>
);

2.3.2 RecoilApp.js #

src\RecoilApp.js

import React, { useRef } from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { todoListState, filteredTodoListState, filterState } from './recoil';

function App() {
  const inputRef = useRef();
  const [todoList, setTodoList] = useRecoilState(todoListState);
  const setFilter = useSetRecoilState(filterState);
  const filteredTodoList = useRecoilValue(filteredTodoListState);

  const addTodo = text => {
    setTodoList([...todoList, {
      text,
      isComplete: false
    }]);
  };

  const toggleTodo = index => {
    setTodoList(oldTodoList => {
      const newTodoList = [...oldTodoList];
      newTodoList[index] = {
        ...newTodoList[index],
        isComplete: !newTodoList[index].isComplete
      };
      return newTodoList;
    });
  };

  const completedTodos = todoList.filter(todo => todo.isComplete).length;

  return (
    <div>
      <input type="text" ref={inputRef} />
      <button onClick={() => addTodo(inputRef.current.value)}>Add</button>

      <div>
        Filter: 
        <select onChange={e => setFilter(e.target.value)}>
          <option value="SHOW_ALL">All</option>
          <option value="SHOW_COMPLETED">Completed</option>
          <option value="SHOW_UNCOMPLETED">Uncompleted</option>
        </select>
      </div>

      <ul>
        {filteredTodoList.map((todo, index) => (
          <li key={index}>
            <input type="checkbox" checked={todo.isComplete} onChange={() => toggleTodo(index)} />
            {todo.isComplete ? <s>{todo.text}</s> : todo.text}
          </li>
        ))}
      </ul>

      <p>Completed Todos: {completedTodos}</p>
    </div>
  );
}

export default App;

2.3.2 atoms.js #

src\recoil\atoms.js

import { atom } from 'recoil';

export const todoListState = atom({
  key: 'todoListState',
  default: [],  // 这是待办事项的数组
});

export const filterState = atom({
  key: 'filterState',
  default: 'SHOW_ALL',
});

在这个例子中,我们使用 recoilatom 函数创建了一个名为 todoListState 的 atom。这个 atom 代表了我们的 todo 列表的状态。它的 key'todoListState',这是一个唯一的字符串,用于在整个应用程序中标识这个 atom。它的 default 值是一个空数组,表示 todo 列表的初始状态是空的。

2.3.3 selectors.js #

src\recoil\selectors.js

import { selector } from 'recoil';
import { todoListState, filterState } from './atoms';
export const filteredTodoListState = selector({
  key: 'filteredTodoListState',
  get: ({ get }) => {
    const todoList = get(todoListState);
    const filter = get(filterState);
    switch (filter) {
      case 'SHOW_COMPLETED':
        return todoList.filter(todo => todo.isComplete);
      case 'SHOW_UNCOMPLETED':
        return todoList.filter(todo => !todo.isComplete);
      case 'SHOW_ALL':
      default:
        return todoList;
    }
  }
});

在这个例子中,我们首先从 recoil 包中导入 selector 函数,然后从 atoms.js 文件中导入 todoListState

接着,我们使用 selector 函数创建了一个新的状态 filteredTodoListState。这个状态是根据 todoListState 计算得来的。selector 函数接受一个对象,该对象包含 keyget 属性。key 是一个字符串,用于标识这个状态。get 是一个函数,它接受一个对象,该对象包含一个 get 函数。我们可以使用这个 get 函数来获取其他的状态。

在这个例子中,我们获取了 todoListState,然后过滤了这个列表,只保留了没有完成的待办事项。最后,get 函数返回这个过滤后的列表。

这样,每当 todoListState 改变时,filteredTodoListState 也会自动更新。

2.3.4 recoil\index.js #

src\recoil\index.js

export * from './atoms';
export * from './selectors';

2.4 jotai #

Jotai 是一个小型、原子化的状态管理库,用于 React。与其他状态管理解决方案(如 Redux、MobX 或 Recoil)相比,Jotai 试图提供一个简洁的 API,并保持轻量。它基于 React 的新的 hooks API,使用了“atom”这一概念来描述状态。

基本概念

1. Atom

在 Jotai 中,状态被分解成多个独立的、可组合的单元,称为 "atom"。每个原子都有一个唯一的 key 和一个默认值。原子是 Jotai 中的最小状态单位,并可以被任何组件订阅。

例如:

import { atom } from 'jotai';

export const countAtom = atom({
  key: 'countState', // 唯一键
  default: 0, // 默认值
});

2. useAtom

useAtom 是一个 hook,允许组件订阅 atom 的值。当 atom 的值发生变化时,所有订阅了该 atom 的组件都会重新渲染。

例如:

import { useAtom } from 'jotai';
import { countAtom } from './atoms';

function Counter() {
  const [count, setCount] = useAtom(countAtom);

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(prev => prev + 1)}>Increment</button>
    </div>
  );
}

3. Selector

除了 atom,Jotai 还提供了 selector,它们是派生状态,也就是说,它们的值是基于一个或多个 atom 的值计算得出的。

例如,基于上面的 countAtom,我们可以创建一个 selector 来判断 count 是否是偶数:

import { selector } from 'jotai';
import { countAtom } from './atoms';

export const doubleSelector = selector({
  key: 'doubleState',
  get: ({ get }) => {
    const count = get(countAtom);
    return count * 2 ;
  },
});

2.5 valtio #

Valtio: 英文文档 是一个非常简单和轻量级的状态管理库,它是基于 JavaScript 的 Proxy 对象构建的。Valtio 的核心概念是 "state" 和 "snapshot"。

state.js 文件中,我们使用 Valtio 的 proxy 函数创建了一个可观察的状态对象。

TodoApp.js 文件中,我们使用 Valtio 的 useSnapshot Hook 创建了一个状态的快照。然后我们使用这个快照来读取 todos 的状态。

useSnapshot Hook 会返回一个只读的、不可变的快照对象。这个快照对象会在状态发生变化时自动更新,但是它不会引起组件的重新渲染。只有当组件中实际使用的状态发生变化时,组件才会重新渲染。

我们在 input 标签的 onChange 事件处理器中直接修改了状态对象。这会触发组件的重新渲染。

2.5.1 ValtioApp.js #

src\ValtioApp.js

import React, { useState } from 'react'; // 引入 React 和 useState Hook
import { useSnapshot } from 'valtio'; // 引入 valtio 的 useSnapshot Hook
import state from './valtio/state'; // 引入 state
// 定义 App 组件
const App = () => {
  const snapshot = useSnapshot(state); // 创建一个 state 的 snapshot
  const [newTodo, setNewTodo] = useState(''); // 创建一个叫做 newTodo 的 state
  // 定义 handleAddTodo 函数
  const handleAddTodo = () => {
    if (newTodo) {
      state.todos.push({ text: newTodo, completed: false }); // 添加新的 todo 到 todos 列表
      setNewTodo(''); // 重置 newTodo
    }
  };
  const completedTodos = snapshot.todos.filter(todo => todo.completed).length; // 计算完成的 todos 的数量
  // 返回 JSX
  return (
    <div>
      <h1>Todo List</h1>
      <input
        type="text"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)} // 当输入变化时更新 newTodo
      />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {snapshot.todos.map((todo, index) => (
          <li key={index}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => (state.todos[index].completed = !todo.completed)} // 当复选框变化时更新 todo 的 completed 状态
            />
            {todo.text}
          </li>
        ))}
      </ul>
      <p>Completed Todos: {completedTodos}</p>
    </div>
  );
};
export default App; // 导出 App 组件

2.5.2 state.js #

src\valtio\state.js

// 引入 valtio 的 proxy 函数
import { proxy } from 'valtio'
// 使用 proxy 创建一个响应式的状态对象
const state = proxy({
  todos: [
    { text: 'Learn React', completed: true },
    { text: 'Learn Valtio', completed: false },
  ],
})
// 导出状态对象
export default state;

2.6 zustand #

Zustand: 英文文档 是一个非常简单且轻量的状态管理库,它避免了 Redux 中的一些复杂性,比如 reducer、action creator 等。它提供了一个可以全局访问的 store 对象,你可以从任何组件中读取状态或者触发更新。

Zustand 的基本使用:

  1. 创建一个 store: 使用 create 函数创建一个 store。
import create from 'zustand';

const useStore = create(set => ({
  count: 0,
  increase: () => set(state => ({ count: state.count + 1 })),
  decrease: () => set(state => ({ count: state.count - 1 })),
}));

在上面的例子中,我们创建了一个包含 countincreasedecrease 的 store。

  1. 在组件中使用 store: 使用自定义的 hook 访问 store。
import useStore from './path_to_your_store';

const Component = () => {
  const { count, increase, decrease } = useStore();

  return (
    <div>
      <button onClick={decrease}>-</button>
      {count}
      <button onClick={increase}>+</button>
    </div>
  );
};

2.6.1 ZustandApp.js #

src\ZustandApp.js

import React, { useState } from 'react';
import useStore from './zustand/store';
const App = () => {
  const { todos, addTodo, toggleTodo } = useStore();
  const [newTodo, setNewTodo] = useState('');
  const handleAddTodo = () => {
    if (newTodo) {
      addTodo(newTodo);
      setNewTodo('');
    }
  };
  const completedTodos = todos.filter(todo => todo.completed).length;
  return (
    <div>
      <h1>Todo List</h1>
      <input
        type="text"
        value={newTodo}
        onChange={(e) => setNewTodo(e.target.value)}
      />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(index)}
            />
            {todo.text}
          </li>
        ))}
      </ul>
      <p>Completed Todos: {completedTodos}</p>
    </div>
  );
};
export default App;

2.6.2 store.js #

src\zustand\store.js

import { create } from 'zustand';
const useStore = create(set => ({
    todos: [],
    addTodo: (text) => set(state => ({
        todos: [...state.todos, { text, completed: false }]
    })),
    toggleTodo: (index) => set(state => {
        const todos = [...state.todos];
        todos[index].completed = !todos[index].completed;
        return { todos };
    })
}));
export default useStore;