谈谈react中的集中状态管理工具

660 阅读26分钟

Mobx

MobX是一个简单而优美的状态管理库,它使得跨组件共享数据变得非常容易。在MobX中,所有的状态都存储在一个单一的可观察对象中,任何对其的修改都可以自动地被反应到使用这些状态的组件上。

主要概念:

  1. 可观察对象(Observable): 它是一个可观察的JavaScript对象,在这个对象上定义的属性将会被MobX跟踪。任何对这个对象的修改都可以被监听到,并且随之自动更新其它依赖这个对象的组件。
  2. 计算值(Computed value): 它是一个函数,返回一个基于可观察对象计算出来的值。当任何依赖的可观察对象发生变化时,计算值会自动更新并返回一个新的值。
  3. Reactions: 它是一个响应函数,接受所有触发副作用的可观察对象作为输入,并且它会在这些可观察对象发生变化时自动执行。可以用它来处理副作用,比如向后端发送请求或者打印日志。
  4. 动作(Action): 它是MobX中唯一允许修改状态的方式。通过使用动作,MobX保证了状态修改的可预测性和可调试性。动作函数的返回值将会被忽略。

总的来说,MobX提供了一种简单而易用的方式来管理前端应用中的状态,能够帮助我们避免繁琐的手动状态管理和数据流跟踪,从而让我们能够更专注于业务逻辑的实现。

简单React组件示例:

import React from 'react';
import { observer } from 'mobx-react';
import { observable, computed, action } from 'mobx';

// 定义可观察对象
class CounterStore {
  // 使用@observable修饰,在这里我们将count定义为一个可观察的值
  @observable count = 0; 

  // 定义计算值
  @computed get doubleCount() {
    return this.count * 2;
  }

  // 定义动作
  @action increment() {
    this.count++;
  }
}

// 将Counter组件定义成一个MobX observer,这样它将会在可观察对象发生变化时重新渲染
@observer
class Counter extends React.Component {
  // 初始化CounterStore
  counterStore = new CounterStore();

  // 处理按钮点击事件并调用动作
  handleButtonClick = () => {
    this.counterStore.increment();
  }

  render() {
    return (
      <div>
        <h1>{this.counterStore.count}</h1>
        <h2>{this.counterStore.doubleCount}</h2>
        <button onClick={this.handleButtonClick}>+ 1</button>
      </div>
    );
  }
}

export default Counter;

**

在上面的代码中,我们定义了一个名为CounterStore的可观察对象,并定义了一个计算值doubleCount和一个动作increment。然后,我们使用@observer修饰Counter组件,并在其中使用CounterStore来跟踪状态,使用handleButtonClick处理按钮点击事件并调用increment动作,在render函数里输出当前的状态和计算值。

observable

@observable 是一个装饰器(Decorator),用于将 class 的属性(或方法)标记为可观察的。在使用 MobX 库进行状态管理时,这个注解在 class 中的属性上经常被使用。

在下面的代码中,我们在一个 class 中使用了 @observable

import { observable } from 'mobx';

class CounterStore {
  @observable count = 0;

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }
}

export default new CounterStore();

**

在这个例子中,我们定义了一个 CounterStore 类,并用 @observable 将 count 属性标记为可观察的。这意味着当 count 的值发生变化时,所有观察者都将得到通知。

实际上,将 @observable 应用于属性的代码,等同于将属性值和一个特殊的 setter 函数绑定在一起。每当 setter 函数被调用以更新属性值时,所有注册的观察者都将被通知。

使用 @observable 可以方便地定义要在 MobX 中使用的可观察属性,从而使系统更加响应式和易于管理。

这样,当我们在组件中调用increment动作时,MobX将自动更新和重新渲染与可观察对象相关的组件,从而使我们可以轻松地管理前端应用中的状态。

让我们来看一个更具体的示例:一个TodoList应用。在这个应用中,我们将使用MobX来管理我们的todo列表中的状态。首先,让我们定义一个TodoListStore类来存储我们的todos列表。我们需要将todos列表定义为一个可观察对象,并且还需要定义一些动作来对它进行修改。

下面是对于TodoListStore的实现:

import { observable, action } from 'mobx';

class TodoListStore {
  // 将todos列表定义为一个可观察对象
  @observable todos = [];

  // 添加todo到列表中
  @action addTodo = (text) => {
    this.todos.push({ text, completed: false });
  }

  // 根据index来标记某个todo已完成
  @action toggleTodo = (index) => {
    this.todos[index].completed = !this.todos[index].completed;
  }

  // 删除指定index的todo
  @action deleteTodo = (index) => {
    this.todos.splice(index, 1);
  }
}

const todoListStore = new TodoListStore();

export default todoListStore;

**

上面的代码中,我们使用@observable修饰了todos属性,这意味着它是一个可观察对象。我们还定义了三个动作:addTodo用于添加一个新的todo到列表中,toggleTodo用于标记某个todo为已完成或未完成,deleteTodo用于删除指定index的todo。通过这些动作,我们将列表的状态管理在TodoListStore中。

接下来,我们将创建一个React组件来呈现我们的todo列表。我们需要使用observer来将组件定义为一个观察者,在其中访问TodoListStore,并根据其状态来更新UI。

下面是一个简单的TodoList组件实现:

import React from 'react';
import { observer } from 'mobx-react';
import todoListStore from './TodoListStore';

@observer
class TodoList extends React.Component {
  handleAddTodo = (e) => {
    if (e.keyCode === 13 && e.target.value) {
      todoListStore.addTodo(e.target.value);
      e.target.value = '';
    }
  }

  handleToggleTodo = (index) => {
    todoListStore.toggleTodo(index);
  }

  handleDeleteTodo = (index) => {
    todoListStore.deleteTodo(index);
  }

  render() {
    return (
      <div>
        <h2>Todo List</h2>
        <input type="text" onKeyDown={this.handleAddTodo} />
        <ul>
          {todoListStore.todos.map((todo, index) => (
            <li key={index}>
              <span
                style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
                onClick={() => this.handleToggleTodo(index)}
              >
                {todo.text}
              </span>
              <button onClick={() => this.handleDeleteTodo(index)}>X</button>
            </li>
          ))}
        </ul>
      </div>
    );
  }
}

export default TodoList;

**

在上面的代码中,我们将TodoList组件定义为一个观察者,这意味着每当TodoListStore中的状态发生变化时,它将自动重新渲染。在组件中,我们使用todoListStore来访问我们的todos列表,并使用map函数输出每个todo及其相关的标记和删除按钮。

一旦我们定义好了TodoList组件和TodoListStore,我们只需要在React应用中使用它们即可。这样,我们就可以通过MobX来轻松地管理我们的前端应用中的状态。

计算属性

实现步骤

  1. 生命一个存在的数据
  2. 通过get关键词 定义计算属性
  3. 在 makeAutoObservable 方法中标记计算属性

counterStore.js

import { computed, makeAutoObservable } from 'mobx'

class CounterStore {
  list = [1, 2, 3, 4, 5, 6]
  constructor() {
    makeAutoObservable(this, {
      filterList: computed
    })
  }
  // 修改原数组
  changeList = () => {
    this.list.push(7, 8, 9)
  }
  // 定义计算属性
  get filterList () {
    return this.list.filter(item => item > 4)
  }
}

const counter = new CounterStore()

export default counter

App.jsx

// 导入counterStore
import counterStore from './store'
// 导入observer方法
import { observer } from 'mobx-react-lite'
function App() {
  return (
    <div className="App">
      {/* 原数组 */}
      {JSON.stringify(counterStore.list)}
      {/* 计算属性 */}
      {JSON.stringify(counterStore.filterList)}
      <button onClick={() => counterStore.changeList()}>change list</button>
    </div>
  )
}
// 包裹组件让视图响应数据变化
export default observer(App)

异步数据处理

实现步骤:

  1. 在mobx中编写异步请求方法 获取数据 存入state中
  2. 组件中通过 useEffect + 空依赖 触发action函数的执行

channelStore.js

import { makeAutoObservable } from 'mobx'
import axios from 'axios'

class ChannelStore {
  channelList = []
  constructor() {
    makeAutoObservable(this)
  }
  // 只要调用这个方法 就可以从后端拿到数据并且存入channelList
  setChannelList = async () => {
    const res = await axios.get('http://geek.itheima.net/v1_0/channels')
    this.channelList = res.data.data.channels
  }
}
const channlStore = new ChannelStore()
export default channlStore

App.jsx

import { useEffect } from 'react'
import { useStore } from './store'
import { observer } from 'mobx-react-lite'
function App() {
  const { channlStore } = useStore()
  // 1. 使用数据渲染组件
  // 2. 触发action函数发送异步请求
  useEffect(() => {
    channlStore.setChannelList()
  }, [])
  return (
    <ul>
      {channlStore.channelList.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}
// 让组件可以响应数据的变化[也就是数据一变组件重新渲染]
export default observer(App)

模块化

场景: 一个项目有很多的业务模块,我们不能把所有的代码都写到一起,这样不好维护,提了提供可维护性,需要引入模块化机制

实现步骤

  1. 拆分模块js文件,每个模块中定义自己独立的state/action
  2. 在store/index.js中导入拆分之后的模块,进行模块组合
  3. 利用React的context的机制导出统一的useStore方法,给业务组件使用

1- 定义task模块

store/taskStore.js

import { makeAutoObservable } from 'mobx'

class TaskStore {
  taskList = []
  constructor() {
    makeAutoObservable(this)
  }
  addTask () {
    this.taskList.push('vue', 'react')
  }
}

const task = new TaskStore()


export default task

2- 定义counterStore

import { makeAutoObservable } from 'mobx'

class CounterStore {
  count = 0
  list = [1, 2, 3, 4, 5, 6]
  constructor() {
    makeAutoObservable(this)
  }
  addCount = () => {
    this.count++
  }
  changeList = () => {
    this.list.push(7, 8, 9)
  }
  get filterList () {
    return this.list.filter(item => item > 4)
  }
}

const counter = new CounterStore()

export default counter

3- 组合模块导出统一方法

import React from 'react'

import counter from './counterStore'
import task from './taskStore'


class RootStore {
  constructor() {
    this.counterStore = counter
    this.taskStore = task
  }
}


const rootStore = new RootStore()

// context机制的数据查找链  Provider如果找不到 就找createContext方法执行时传入的参数
const context = React.createContext(rootStore)

const useStore = () => React.useContext(context)
// useStore() =>  rootStore  { counterStore, taskStore }

export { useStore }

4- 组件使用模块中的数据

import { observer } from 'mobx-react-lite'
// 导入方法
import { useStore } from './store'
function App() {
  // 得到store
  const store = useStore()
  return (
    <div className="App">
      <button onClick={() => store.counterStore.addCount()}>
        {store.counterStore.count}
      </button>
    </div>
  )
}
// 包裹组件让视图响应数据变化
export default observer(App)

多组件共享数据

目标:当数据发生变化 所有用到数据的组件都会得到同步的组件的更新

实现步骤:在Foo组件和Bar组件中分别使用store中的数据,然后在app组件中进行数据修改,查看Foo组件和Bar组件是否得到更新

Bar.jsx

// 用taskStore中的taskList数据
import { useStore } from './store'
import { observer } from 'mobx-react-lite'
const Bar = () => {
  const { taskStore } = useStore()
  return (
    <ul>
      {taskStore.taskList.map((item) => (
        <li>{item}</li>
      ))}
    </ul>
  )
}

export default observer(Son)

Foo.jsx

// 用taskStore中的taskList数据
import { useStore } from './store'
import { observer } from 'mobx-react-lite'
const Bar = () => {
  const { taskStore } = useStore()
  return (
    <ul>
      {taskStore.taskList.map((item) => (
        <li>{item}</li>
      ))}
    </ul>
  )
}

export default observer(Son)

App.jsx

import Bar from './Bar'
import Foo from './Foo'
import { useStore } from './store'
function App() {
  const { taskStore } = useStore()
  return (
    <div className="App">
      <Bar />
      <button onClick={() => taskStore.setTaskList('angular')}>
        修改taskStore
      </button>
    </div>
  )
}
export default App

Context

在 React 中,Context 是一种用于在组件树中共享数据的方式。它允许您在不显式地将 prop 传递到每个组件的情况下,向组件树中的所有组件传递数据。

Context 由两个部分组成:Provider 和 Consumer

Provider 是 Context 对象的生产者,它使用 value 属性传递数据(可以是任意类型的数据)。当 Provider 中的 value 值发生变化时,它会把新的 value 值传递给内部的 Consumer 组件。

Consumer 是 Context 对象的消费者,它需要访问父组件中定义的数据时,可以通过 Consumer 组件来订阅数据。当 Provider 中的 value 值发生变化时,与此 Context 相关的 Consumer 组件都将重新渲染。

下面是一个简单的例子,演示了如何使用 Context 共享数据:

// App.js
import React, { createContext, useState } from 'react';
import ComponentA from './ComponentA';

// 创建 Context
export const CountContext = createContext();

function App() {
  // 使用 useState 定义 count 状态和更新函数 setCount
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>App Component</h1>
      {/* 使用 Provider 将 count 值传递给所有子组件 */}
      <CountContext.Provider value={{ count, setCount }}>
        <ComponentA />
      </CountContext.Provider>
    </div>
  );
}

export default App;

**

在这个例子中,我们创建了一个名为 CountContext 的 Context 对象,并使用 useState 钩子定义了一个状态变量 count 和一个更新状态的函数 setCount。在 App 组件中通过 Provider 来将 count 值和 setCount 函数传递给 ComponentA 组件以及其所有子组件。

下面是 ComponentA 组件:

// ComponentA.js
import React, { useContext } from 'react';
import { CountContext } from './App';

function ComponentA() {
  // 使用 useContext 订阅 count 值和 setCount 函数
  const { count, setCount } = useContext(CountContext);

  return (
    <div>
      <h2>Component A</h2>
      <p>Count: {count}</p>
      {/* 点击按钮更新 count 值 */}
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

export default ComponentA;

**

在 ComponentA 组件中,我们使用 useContext 钩子来订阅 CountContext 中的 count 值和 setCount 函数。这样就可以直接在 ComponentA 组件中使用这些值,而不需要在 ComponentA 的父组件中将它们作为 prop 传递下来。

以上就是一个简单的 Context 示例。使用 Context 可以方便地在组件树中共享数据,避免了一层层的 prop 传递,使代码变得更加简洁和易于维护。

下面是一个稍微复杂一点的 Context 代码示例,演示了如何使用 Context 和 Reducer 来实现一个状态管理器:

// StateContext.js
import React, { createContext, useContext, useReducer } from 'react';

// 创建 Context
export const StateContext = createContext();

// 定义初始状态
const initialState = {
  count: 0,
  isLightOn: true,
};

// 定义 reducer 函数
function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'TOGGLE_LIGHT':
      return { ...state, isLightOn: !state.isLightOn };
    default:
      return state;
  }
}

// 创建 StateProvider 组件,提供数据和 dispatch 函数给子组件
export function StateProvider({ children }) {
  // 使用 useReducer 定义 state 和 dispatch 函数
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <StateContext.Provider value={{ state, dispatch }}>
      {children}
    </StateContext.Provider>
  );
}

// 使用 useContext 共享 state 和 dispatch 函数
export function useGlobalState() {
  const { state, dispatch } = useContext(StateContext);
  return { state, dispatch };
}

**

在这个例子中,我们创建了一个名为 StateContext 的 Context 对象,并将其作为数据和 dispatch 函数的提供者。使用 useReducer 钩子定义了一个初始状态 initialState 和一个 reducer 函数 reducer,用于处理各种 action。创建 StateProvider 组件使用 Provider 将 state 和 dispatch 传递给所有子组件。使用 useContext 钩子定义了一个 useGlobalState 自定义 hooks,用于在子组件中共享 state 和 dispatch

下面是一个使用这个 Context 的组件:

// Example.js
import React from 'react';
import { useGlobalState } from './StateContext';

function Example() {
  // 使用 useGlobalState 钩子订阅 state 和 dispatch
  const { state, dispatch } = useGlobalState();

  return (
    <div>
      <h2>Example Component</h2>
      <p>Count: {state.count}</p>
      <p>Light: {state.isLightOn ? 'On' : 'Off'}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
      <button onClick={() => dispatch({ type: 'TOGGLE_LIGHT' })}>Toggle Light</button>
    </div>
  );
}

export default Example;

**

在 Example 组件中,我们使用 useGlobalState 钩子来订阅 StateContext 中的 state 和 dispatch,并在组件中使用它们来更新和渲染状态。

以上就是全部介绍了,它使用了 Reducer 和自定义 hooks,可以实现更加复杂的状态管理。在实际开发中,Context 和 Reducer 的结合使用可以帮助我们更好地组织和管理组件状态,使代码更加清晰和可维护。

Redux

Redux是一个流行的状态管理库,它可以让你在整个应用程序中存储和更新全局状态。Redux中的state通过一个store对象来维护,而不是存储在组件中。Redux使用单一的状态树和纯函数来实现应用程序状态的可预测性。在React中使用Redux时,需要使用react-redux提供的Provider组件,以使store可以在整个应用中访问。

下面是一个简单的示例:

import React from 'react';
import { createStore } from 'redux';
import { Provider, connect } from 'react-redux';

// 创建状态对象
const initialState = {
  theme: 'light',
};

// 创建动作类型
const CHANGE_THEME = 'CHANGE_THEME';

// 定义动作,一个动作包括type字段和payload字段
const changeTheme = (theme) => ({
  type: CHANGE_THEME,
  payload: theme,
});

// 创建Reducer
const reducer = (state = initialState, action) => {
  switch(action.type) {
    case CHANGE_THEME:
      return { ...state, theme: action.payload }
    default:
      return state;
  }
};

首先,redux并不是必须的,它的作用相当于在顶层组件之上又加了一个组件,作用是进行逻辑运算、储存数据和实现组件尤其是顶层组件的通信。如果组件之间的交流不多,逻辑不复杂,只是单纯的进行视图的渲染,这时候用回调,context就行,没必要用redux,用了反而影响开发速度。但是如果组件交流特别频繁,逻辑很复杂,那redux的优势就特别明显了。我第一次做react项目的时候并没有用redux,所有的逻辑都是在组件内部实现,当时为了实现一个逻辑比较复杂的购物车,洋洋洒洒居然写了800多行代码,回头一看我自己都不知道写的是啥,画面太感人。

先简单说一下redux和react是怎么配合的。react-redux提供了connect和Provider两个好基友,它们一个将组件与redux关联起来,一个将store传给组件。组件通过dispatch发出action,store根据action的type属性调用对应的reducer并传入state和这个action,reducer对state进行处理并返回一个新的state放入store,connect监听到store发生变化,调用setState更新组件,此时组件的props也就跟着变化。

值得注意的是connect,Provider,mapStateToProps,mapDispatchToProps是react-redux提供的,redux本身和react没有半毛钱关系,它只是数据处理中心,没有和react产生任何耦合,是react-redux让它们联系在一起。

接下来具体分析一下,redux以及react-redux到底是怎么实现的。

redux主要由三部分组成:store,reducer,action。

store是一个对象,它有四个主要的方法:

1、dispatch:

用于action的分发——在createStore中可以用middleware中间件对dispatch进行改造,比如当action传入dispatch会立即触发reducer,有些时候我们不希望它立即触发,而是等待异步操作完成之后再触发,这时候用redux-thunk对dispatch进行改造,以前只能传入一个对象,改造完成后可以传入一个函数,在这个函数里我们手动dispatch一个action对象,这个过程是可控的,就实现了异步。

2、subscribe:

监听state的变化——这个函数在store调用dispatch时会注册一个listener监听state变化,当我们需要知道state是否变化时可以调用,它返回一个函数,调用这个返回的函数可以注销监听。 let unsubscribe = store.subscribe(() => {console.log('state发生了变化')})

3、getState:

获取store中的state——当我们用action触发reducer改变了state时,需要再拿到新的state里的数据,毕竟数据才是我们想要的。getState主要在两个地方需要用到,一是在dispatch拿到action后store需要用它来获取state里的数据,并把这个数据传给reducer,这个过程是自动执行的,二是在我们利用subscribe监听到state发生变化后调用它来获取新的state数据,如果做到这一步,说明我们已经成功了。

4、replaceReducer:

替换reducer,改变state修改的逻辑。

注:store.subscribe 用来注册一个状态变化监听器(或称为订阅者)。

在 Redux 中,我们通过 store 对象来管理应用的所有状态,这些状态存储在一个数据仓库中。当应用内部或外部发生某种事件/动作(比如用户点击按钮)导致数据仓库中的某个状态被修改时,需要通知应用中的各个模块或组件进行相关的响应操作。这便是 Redux 的数据流机制中所谓的“订阅发布模式”,其中 store.subscribe() 扮演的便是“订阅者”的角色。

当你调用 store.subscribe(listener) 方法来注册一个监听器时,Redux 内部会将这个监听器添加到一个监听队列中,每当数据仓库中的某个状态发生变化时,Redux 会遍历这个队列并触发所有监听器回调方法,通知它们进行相关的响应操作。

下面是一个具体的例子:

const store = createStore(reducer);

const listener = () => {
  console.log('状态变化了,新的状态是:', store.getState());
}

store.subscribe(listener);

store.dispatch({ type: 'INCREMENT' }); // 输出:状态变化了,新的状态是: { count: 1 }
store.dispatch({ type: 'DECREMENT' }); // 输出:状态变化了,新的状态是: { count: 0 }

**

在上面的例子中,我们首先通过 createStore(reducer) 方法创建了一个 Redux 的 store 对象,并定义了一个状态变化的监听器 listener。然后,我们调用 store.subscribe(listener) 方法来将这个监听器注册到 store 对象上。最后,我们通过 store.dispatch(action) 方法来分别触发两个状态变化的事件,并在控制台输出了新的状态值。

可以看到,当我们触发状态变化事件时,listener 监听器被触发,输出了当前的新状态值。这表明 listener 成功订阅了 store 对象中的状态变化事件,并在状态值发生变化时得到了通知。 store可以通过createStore()方法创建,接受三个参数,经过combineReducers合并的reducer和state的初始状态以及改变dispatch的中间件,后两个参数并不是必须的。store的主要作用是将action和reducer联系起来并改变state。

mapDispatchToProps、mapStateToProps、connect()

connect() 接收四个参数,它们分别是 mapStateToProps , mapDispatchToProps , mergeProps 和 options 。

是Redux中一个辅助函数,用于将action creator转换为dispatch函数,并将其映射到组件的props中。它接收一个对象作为参数,对象中的每个属性都是一个action creator,它把这个action creator转换成一个函数,这个函数会调用store的dispatch方法,并将返回的action对象发送到store。这样,我们就可以通过组件的props来调用这些action creator,而不必手动调用dispatch方法,减少了代码的冗余和重复性。例如:

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { fetchData } from './actions';

const mapStateToProps = (state) => {
  return {
    data: state.data,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    fetchData: bindActionCreators(fetchData, dispatch),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

**

在上面的例子中,fetchData将被映射到MyComponent的props中,并且可以通过this.props.fetchData来调用。当我们调用fetchData时,它会自动转换为一个dispatch函数,并将一个fetchData的action对象发送到store中。这样,我们就可以在组件中实现Redux的数据流。

  mapStateToProps(state, ownProps) : stateProps
  这个函数允许我们将 store 中的数据作为 props 绑定到组件上。

  const mapStateToProps = (state) => {
    return {
    count: state.count
   }
  }

这个函数的第一个参数就是 Redux 的 store ,我们从中摘取了 count 属性。因为返回了具有 count 属性的对象,所以 MyComp 会有名为 count 的 props 字段。

action:

action是一个对象,其中type属性是必须的,同时可以传入一些数据。action可以用actionCreactor进行创造。dispatch就是把action对象发送出去。

reducer:

reducer是一个函数,它接受一个state和一个action,根据action的type返回一个新的state。根据业务逻辑可以分为很多个reducer,然后通过combineReducers将它们合并,state树中有很多对象,每个state对象对应一个reducer,state对象的名字可以在合并时定义。

const reducer = combineReducers({
     a: doSomethingWithA,
     b: processB,
     c: c
})

combineReducers:

其实它也是一个reducer,它接受整个state和一个action,然后将整个state拆分发送给对应的reducer进行处理,所有的reducer会收到相同的action,不过它们会根据action的type进行判断,有这个type就进行处理然后返回新的state,没有就返回默认值,然后这些分散的state又会整合在一起返回一个新的state树。

接下来分析一下整体的流程, (1)首先调用store.dispatch将action作为参数传入,同时用getState获取当前的状态树state并注册subscribe的listener监听state变化

(2)再调用combineReducers并将获取的state和action传入。combineReducers会将传入的state和action传给所有reducer

(3)并根据action的type返回新的state,触发state树的更新,我们调用subscribe监听到state发生变化后用getState获取新的state数据。

redux的state和react的state两者完全没有关系,除了名字一样。

下面是一个范例,首先定义一个 action creators,它返回一个 action 对象:

// actions.js
export const addTodo = (text) => {
  return {
    type: 'ADD_TODO',
    text,
  };
};

然后,定义一个 reducer 函数,用于处理各种 action:

// todos.js
const initialState = [];

const todosReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: Date.now(),
          text: action.text,
          completed: false,
        },
      ];
    case 'TOGGLE_TODO':
      return state.map((todo) =>
        todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
      );
    default:
      return state;
  }
};

export default todosReducer;

**

这个 reducer 处理了两个 action,分别是添加 todo 和切换 todo 的状态。

接下来,用 combineReducers() 函数将不同的 reducer 函数组合起来,并创建一个 Redux store:

// index.js
import { createStore, combineReducers } from 'redux';
import todosReducer from './todos';

const rootReducer = combineReducers({
  todos: todosReducer,
});

const store = createStore(rootReducer);

export default store;

**

最后,在 React 组件中使用 store 和 action creators:

// TodoApp.jsx
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addTodo } from '../actions'
import TodoList from './TodoList'

function TodoApp() {
  const [text, setText] = useState("");
  const dispatch = useDispatch();
  const todos = useSelector(state => state.todos); // 从 store 中获取 todos 数组

  const handleClick = () => {
    dispatch(addTodo(text)); // 调用 addTodo action creator 来添加 todo
    setText(""); // 清空输入框
  };

  return (
    <>
      <h1>Todo List</h1>
      <div>
        <input value={text} onChange={(e) => setText(e.target.value)} />
        <button onClick={handleClick}>Add Todo</button>
      </div>
      <TodoList todos={todos} />
    </>
  );
}

export default TodoApp;

**

在这个组件中,当用户点击 “Add Todo” 按钮时,它将调用 addTodo action creator 来添加一个新的 todo,同时更新 Redux Store 中的 todo 状态。通过 useSelector hook,组件从 store 中获取当前的 todos 数组并将其传递给 TodoList 组件进行渲染。

以上就是一个比较复杂的 Redux 示例。这个示例包括定义 action、reducer、store 和在 React 组件中使用 store 的完整过程,相信可以帮助你更好的理解 Redux。

React-Redux

React-Redux是React框架的一个扩展库,它提供了一种解决React与Redux数据管理的结合问题的方法。Redux是React应用程序的预选状态管理库,用于管理应用程序内的所有状态。React与Redux的结合,可以大大简化数据状态的管理和传递方式。

React-Redux的主要作用是将Redux与React无缝集成。它提供了一个Provider和connect函数,用于连接React和Redux。connect函数是一个高阶组件,用于将Redux store的状态和action连接到组件props上。它使得组件能够轻松地访问Redux store的状态和行为,并对组件进行更好的管理和控制。

使用React-Redux可以使开发者更加注重组件的构建,而无需关注状态管理的细节。同时,该库为数据状态提供了一个单一的集中化存储位置,使得应用程序的状态发生变化更加容易追踪,并且更加易于调试。

总之,React-Redux大大简化了React与Redux的结合问题,使得开发者可以更加专注于组件的构建中,减少了代码实现的复杂性。

以下是一个简单的示例,演示如何使用React-Redux连接一些组件和Redux store:

// Store.js
import { createStore } from 'redux';

const initialState = { count: 0 };

function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

const store = createStore(counterReducer);

export default store;

**

// Counter.js
import { useDispatch, useSelector } from 'react-redux';

function Counter() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>-</button>
    </div>
  );
}

export default Counter;

**

// App.js
import { Provider } from 'react-redux';
import store from './Store';
import Counter from './Counter';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

**

在上面的示例中,store.js文件中创建了一个名为counterReducer的Redux reducer,并使用createStore函数创建了一个Redux store。在Counter.js文件中,使用useSelector钩子从Redux store中获取count状态,并使用useDispatch钩子分发名为INCREMENT和DECREMENT的action。最后,在App.js文件中,使用Provider组件将Redux store传递给Counter组件。

这是React-Redux简单的示例,演示了如何将React组件与Redux store相连接以跟踪状态更改。

// Store.js
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import userReducer from './reducers/userReducer';
import postReducer from './reducers/postReducer';

const rootReducer = combineReducers({
  user: userReducer,
  post: postReducer,
});

const store = createStore(rootReducer, applyMiddleware(thunk));

export default store;

**

// userReducer.js
const initialState = {
  user: null,
  isLoading: false,
  error: null,
};

function userReducer(state = initialState, action) {
  switch (action.type) {
    case 'FETCH_USER_REQUEST':
      return { ...state, isLoading: true };
    case 'FETCH_USER_SUCCESS':
      return { ...state, user: action.payload, isLoading: false, error: null };
    case 'FETCH_USER_ERROR':
      return { ...state, isLoading: false, error: action.payload };
    default:
      return state;
  }
}

export default userReducer;

**

// postReducer.js
const initialState = {
  posts: [],
  isLoading: false,
  error: null,
};

function postReducer(state = initialState, action) {
  switch (action.type) {
    case 'FETCH_POSTS_REQUEST':
      return { ...state, isLoading: true };
    case 'FETCH_POSTS_SUCCESS':
      return { ...state, posts: action.payload, isLoading: false, error: null };
    case 'FETCH_POSTS_ERROR':
      return { ...state, isLoading: false, error: action.payload };
    default:
      return state;
  }
}

export default postReducer;

**

// userActions.js
function fetchUserRequest() {
  return { type: 'FETCH_USER_REQUEST' };
}

function fetchUserSuccess(user) {
  return { type: 'FETCH_USER_SUCCESS', payload: user };
}

function fetchUserError(error) {
  return { type: 'FETCH_USER_ERROR', payload: error };
}

export function fetchUser() {
  return async (dispatch) => {
    dispatch(fetchUserRequest());
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
      const data = await response.json();
      dispatch(fetchUserSuccess(data));
    } catch (error) {
      dispatch(fetchUserError(error.message));
    }
  };
}

**

// postActions.js
function fetchPostsRequest() {
  return { type: 'FETCH_POSTS_REQUEST' };
}

function fetchPostsSuccess(posts) {
  return { type: 'FETCH_POSTS_SUCCESS', payload: posts };
}

function fetchPostsError(error) {
  return { type: 'FETCH_POSTS_ERROR', payload: error };
}

export function fetchPosts() {
  return async (dispatch) => {
    dispatch(fetchPostsRequest());
    try {
      const response = await fetch('https://jsonplaceholder.typicode.com/posts');
      const data = await response.json();
      dispatch(fetchPostsSuccess(data));
    } catch (error) {
      dispatch(fetchPostsError(error.message));
    }
  };
}

**

// User.js
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser } from './actions/userActions';

function User() {
  const user = useSelector((state) => state.user.user);
  const isLoading = useSelector((state) => state.user.isLoading);
  const error = useSelector((state) => state.user.error);

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchUser());
  }, [dispatch]);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>{error}</p>;
  }

  return (
    <div>
      <h2>User</h2>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <p>Phone: {user.phone}</p>
    </div>
  );
}

export default User;

**

// Posts.js
import { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchPosts } from './actions/postActions';

function Posts() {
  const posts = useSelector((state) => state.post.posts);
  const isLoading = useSelector((state) => state.post.isLoading);
  const error = useSelector((state) => state.post.error);

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchPosts());
  }, [dispatch]);

  if (isLoading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>{error}</p>;
  }

  return (
    <div>
      <h2>Posts</h2>
      {posts.map(post => (
        <div key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  );
}

export default Posts;

**

在上面的示例中,定义了一个包含多个reducer的rootReducer,将其传递给createStore函数来创建Redux store,然后将store传递给Provider组件,使得所有组件都能够访问到该store。User.js和Posts.js两个组件分别使用useSelector钩子从Redux store中选择需要的状态,同时使用useDispatch钩子来调度fetchUser和fetchPosts异步操作。fetchUser和fetchPosts是通过thunk middleware来支持异步的action creators。

在应用程序中包含多个reducer可以将各自的状态划分为各自的领域,其中每个领域托管的数据都有其自己的reducer处理。同时这个代码示例还演示了如何在同一个组件中处理多个loading和error状态,使得应用程序的用户体验更加稳定和流畅。

Redux和react-Redux的关系

Redux 是一个用于管理应用程序状态的 JavaScript 库,它独立于 React,可以与其他 JavaScript 库和框架一起使用。React-redux 是一个用于在 React 应用程序中使用 Redux 状态管理的官方库,是建立在 Redux 之上的。

React-redux 提供了一些用于与 React 应用程序集成的 API,例如 Provider 组件和 connect() 高阶函数。它的主要功能是将 Redux 库集成到 React 中,以便在 React 应用程序中管理状态,并使对 Redux 库中数据的修改可以同步到 React 应用程序的视图中。

同时,React-redux 也提供了一些优化性能的功能,例如 connect() 函数可以连接 React 组件和 Redux store,并在 store 中发生变化时自动更新组件,减少了手动操作的部分,使代码更简洁可读。

项目搭建

1、先引用 react.js,redux,react-router 等基本文件,建议用npm安装,直接在文件中引用。

2、从 react.js,redux,react-router 中引入所需要的对象和方法。

import React, {Component, PropTypes} from 'react';
import ReactDOM, {render} from 'react-dom';
import {Provider, connect} from 'react-redux';
import {createStore, combineReducers, applyMiddleware} from 'redux';
import { Router, Route, Redirect, IndexRoute, browserHistory, hashHistory } from 'react-router';

3、根据需求创建顶层ui组件,每个顶层ui组件对应一个页面。

4、创建actionCreators和reducers,并用combineReducers将所有的reducer合并成一个大的reduer。利用createStore创建store并引入combineReducers和applyMiddleware。

5、利用connect将actionCreator,reuder和顶层的ui组件进行关联并返回一个新的组件。

6、利用connect返回的新的组件配合react-router进行路由的部署,返回一个路由组件Router。

7、将Router放入最顶层组件Provider,引入store作为Provider的属性。

8、调用render渲染Provider组件且放入页面的标签中。

可以看到顶层的ui组件其实被套了四层组件,Provider,Router,Route,Connect,这四个组件并不会在视图上改变react,它们只是功能性的。

下面是一个较为简单的使用React和Redux实现的代码案例: 实现一个Todo List应用,可以添加、删除、标记完成、筛选不同状态的任务。

首先,我们需要安装React和Redux库,并创建一个React项目。在项目中安装redux和react-redux库。

npm install redux react-redux

接着,创建App组件和reducer文件。

// App.js

import React from 'react';
import { connect } from 'react-redux';
import { addTask, deleteTask, toggleTask, setVisibilityFilter } from './actions';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      inputText: ''
    };
    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleInputChange(event) {
    this.setState({ inputText: event.target.value });
  }

  handleSubmit(event) {
    event.preventDefault();
    const completed = false;
    const id = this.props.tasks.length + 1;
    const { addTask } = this.props;
    addTask(this.state.inputText, completed, id);
    this.setState({ inputText: '' });
  }

  render() {
    const { tasks, deleteTask, toggleTask, visibilityFilter, setVisibilityFilter } = this.props;

    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <input value={this.state.inputText} onChange={this.handleInputChange} />
          <button type="submit">Add Task</button>
        </form>
        <ul>
          {tasks.filter(({ completed }) => {
            if (visibilityFilter === 'COMPLETED') return completed;
            if (visibilityFilter === 'INCOMPLETE') return !completed;
            return true;
          }).map(({ text, completed, id }) => (
            <li key={id}>
              <span
                style={
                  completed
                    ? { textDecoration: 'line-through', color: 'gray' }
                    : { textDecoration: 'none', color: 'black' }
                }
                onClick={() => toggleTask(id)}
              >
                {text}
              </span>
              <button onClick={() => deleteTask(id)}>Remove</button>
            </li>
          ))}
        </ul>
        <div>
          Show:
          <button onClick={() => setVisibilityFilter('ALL')}>All</button>
          <button onClick={() => setVisibilityFilter('COMPLETED')}>Completed</button>
          <button onClick={() => setVisibilityFilter('INCOMPLETE')}>Incomplete</button>
        </div>
      </div>
    );
  }
}

const mapStateToProps = ({ tasks, visibilityFilter }) => ({ tasks, visibilityFilter });
const mapDispatchToProps = { addTask, deleteTask, toggleTask, setVisibilityFilter };
export default connect(mapStateToProps, mapDispatchToProps)(App);

**

// reducer.js

import { ADD_TASK, DELETE_TASK, TOGGLE_TASK, SET_VISIBILITY_FILTER } from './actionTypes';

const initialState = {
  tasks: [],
  visibilityFilter: 'ALL'
};

const taskReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TASK:
      return {
        ...state,
        tasks: [
          ...state.tasks,
          {
            text: action.text,
            completed: action.completed,
            id: action.id
          }
        ]
      };
    case DELETE_TASK:
      return {
        ...state,
        tasks: state.tasks.filter(({ id }) => id !== action.id)
      };
    case TOGGLE_TASK:
      return {
        ...state,
        tasks: state.tasks.map(task =>
          task.id === action.id ? { ...task, completed: !task.completed } : task
        )
      };
    case SET_VISIBILITY_FILTER:
      return { ...state, visibilityFilter: action.filter };
    default:
      return state;
  }
};

export default taskReducer;

最后,我们需要创建action creator和action types。

// actions.js

import { ADD_TASK, DELETE_TASK, TOGGLE_TASK, SET_VISIBILITY_FILTER } from './actionTypes';

export const addTask = (text, completed, id) => ({
  type: ADD_TASK,
  text,
  completed,
  id
});

export const deleteTask = id => ({
  type: DELETE_TASK,
  id
});

export const toggleTask = id => ({
  type: TOGGLE_TASK,
  id
});

export const setVisibilityFilter = filter => ({
  type: SET_VISIBILITY_FILTER,
  filter
});

**

// actionTypes.js

export const ADD_TASK = 'ADD_TASK';
export const DELETE_TASK = 'DELETE_TASK';
export const TOGGLE_TASK = 'TOGGLE_TASK';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';