# 共享状态思想

共享状态的思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。基于这个思想来分析下几种实现方式

# 全局

最简单的处理就是把状态存到一个外部变量里面,比如:this.$root.$data,当然也可以是一个全局变量。但是这样有一个问题,就是数据改变后,不会留下变更过的记录,这样不利于调试

# Store 模式

针对全局方式产生的问题,那就是换一种更改状态的思路:不直接修改全局状态,而是通过中间函数去修改状态

var store = {
  state: {
    message: 'Hello!'
  },
  setMessageAction (newValue) {
    // 发生改变记录点日志啥的
    this.state.message = newValue
  },
  clearMessageAction () {
    this.state.message = ''
  }
}

store 的 state 来存数据,store 中还里面有一些方法(取名action),这些 action 来控制 state 的改变,也就是不直接去对 state 做改变,而是通过 action 来改变,因为都走 action,我们就可以知道到底改变(mutation)是如何被触发的,出现错误,也可以记录记录日志啥的。

总结一下思想组件: 不允许直接修改属于 store 实例的 state,组件里面应该执行 action 来分发 (dispatch) 事件通知 store 去改变。

这样一个简单的 Flux 架构就实现了

# Flux

Flux其实是一种思想,就像MVC,MVVM之类的,他给出了一些基本概念,所有的框架都可以根据他的思想来做一些实现

Flux把一个应用分成了4个部分: View、Action、Dispatcher、Store

   ┌───────────────────────────┐
┌─>│           Action          │
│  └─────────────┬─────────────┘
|                ↓
│  ┌─────────────┴─────────────┐
│  │         Dispatcher        │
│  └─────────────┬─────────────┘
|                ↓
│  ┌─────────────┴─────────────┐
│  │           Store           │
│  └─────────────┬─────────────┘  
|                ↓
│  ┌─────────────┴─────────────┐
└──┤           View            │
   └───────────────────────────┘
  • Dispatcher:调度器,接收到Action,并将它们发送给Store。

  • Action:动作消息,包含动作类型与动作描述。

  • Store:数据中心,持有应用程序的数据,并会响应Action消息。

  • View:应用视图,可展示Store数据,并实时响应Store的更新。

Flux 要求,View 要想修改 Store,必须经过一套流程

  1. 视图产生动作消息(Actions),将动作传递给调度器(Dispatcher)

  2. 调度器将动作消息发送给每一个数据中心

  3. 数据中心再将数据传递给视图

Flux 思想的好处:

  1. 视图组件变得很薄,只包含了渲染逻辑和触发 action 这两个职责

  2. 要理解一个 store 可能发生的状态变化,只需要看它所注册的 actions 回调就可以

  3. 任何状态的变化都必须通过 action 触发,而 action 又必须通过 dispatcher 走,所以整个应用的每一次状态变化都会从同一个地方流过

所以Flux的最大特点就是数据都是单向流动的

# Redux

Redux实现与Flux架构大致类似,但在实现上有一些不同:

  • Redux并没有 dispatcher。它依赖纯函数来替代事件处理器,也不需要额外的实体来管理它们

  • Redux为不可变数据集。在每次Action请求触发以后,Redux都会生成一个新的对象来更新State,而不是在当前状态上进行更改

  • Redux有且只有一个Store对象。它的Store储存了整个应用程序的State

# Store

Redux 里面只有一个 Store,整个应用的数据都在这个大 Store 里面。Store 的 State 不能直接修改,每次只能返回一个新的 State。Redux 整了一个 createStore 函数来生成 Store

import { createStore } from 'redux';
const store = createStore(fn);

Store 允许使用 store.subscribe 方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。这样不管 View 是用什么实现的,只要把 View 的更新函数 subscribe 一下,就可以实现 State 变化之后,View 自动渲染了

# Action

在Redux中,Action 是一个纯粹的 JavaScript 对象,用于描述Store 的数据变更信息,简单来说,所有数据变化都来源于 Actions

在 Action 对象中,必须有一个字段type用于描述操作类型,他们的值为字符串类型

{
    type: 'ADD_TODO',
    payload: {
      text: 'Do something.'
    }
    // .. pass item
}

Action Creator

在Redux中,Action Creator是用于创建动作的函数,它会返回一个Action对象:

function addTodo(text) {
  return {
    type: 'ADD_TODO',
    payload: {
      text,
    }
  }
}

与Flux所不同的是,在Flux 中Action Creator 同时会负责触发 dispatch 操作,而Redux只负责创建Action,实际的派发操作由 store.dispatch 方法执行:store.dispatch(addTodo('something')),这使得Action Creator的行为更简单也便于测试

# bindActionCreators

通常我们不会直接使用store.dispatch方法派发 Action,而是使用connect方法获取dispatch派发器,并使用bindActionCreators将Action Creators自动绑定到dispatch函数中:

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

function mapDispatchToProps(dispatch) {
  return bindActionCreators(
    { addTodo },
    dispatch
  );
}

const Todo = ({ addTodo }) => {}
export default connect(null, mapDispatchToProps)(Todo);

通过 bindActionCreators 之后,我们可以将这些 Action Creators 传递给子组件,子组件不需要去获取 dispatch 方法,而是直接调用该方法即可触发Action。

# Reducers

对于Action来讲,它们只是描述了发生了什么事情,而应用程序状态的变化,全由Reducers进行操作更改

在实现Reducer函数之前,首先需要定义应用程序中State的数据结构,它被储存为一个单独的对象中,因此在设计它的时候,尽量从全局思维去考虑,并将其从逻辑上划分为不同的模块,采用最小化、避免嵌套,并将数据与UI状态分别存储。

Reducer是一个纯函数,它会结合先前的state状态与Action对象来生成的新的应用程序状态树:

(previousState, action) => newState

内部一般通过 switch...case 语句来处理不同的Action

Reducer函数会作为createStore的第一个参数,并且在第一次调用reducer时,state参数为undefined,因此我们也需要有初始化State的方法。举一个示例:

const initialState = { count: 0 }

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

# Redux 小结

Store 就是我们的保存数据的地方

Reducer 是更新Store的地方,通过 result 进行更新

Action 是告诉 Reducer 如何更新 Store,Reducer 根据 Action 做相应的更新

let initialState = {
  name: 'no',
  age: 35,
}
function todoUser(state = initialState, action) {
  switch (action.type) {
    case 'updateName':
      return Object.assign({}, state, {
        name: action.value
      })
    case 'updateAge':
      return Object.assign({}, state, {
        age: action.value
      })
    default:
      return state
  }
}

todoUser 就是一个 Reducer ,它根据 action 内容返回 state

action 说白了就是一个普通对象,在当前例子中,一个 action 应该至少包含两个属性:

  • type: 告诉要更新哪个属性

  • value:更新的值

eg:

{
  type: 'updateName',
  value: 'lanjz'
}

上面这个对象结构就是一个 Action 了

如果推送一个 Action 给 Reducer 呢? 通过 dispatch 方法。如 store.dispatch({ type: 'updateName', value: 'lanjz'})

# 结合 React

https://juejin.cn/post/6844903955739197447

创建一个 Reducer

// config.js
const defaultState = {
	dict: {},
    other: {}
}
const config = (state= defaultState, action) => {
	switch (action.type) {
		case 'UPDATE_DICT':
			let { key, data } = action
			let newDict = {
				...state.dict,
				[key]: data
			}
			return Object.assign({}, state, {
				dict: newDict
			})
		default:
			return state
	}
}

根据 Reducer 创建一个 Store

import { combineReducers, createStore  } from 'redux'
import config from './config'

let reduces = combineReducers( {config} )
export default createStore(reduces)

因为一个应用可能存多个 reduces 所以使用 combineReducers 并行合并,最后使用 createStore 创建一个 Store

添加 Store 到 React 应用

import { BrowserRouter } from 'react-router-dom'
import { renderRoutes } from 'react-router-config';
import { routes } from './route/index'
import {ConfigProvider} from "antd";
import zhCN from "antd/lib/locale/zh_CN";
import React from "react";
import { Provider } from "react-redux";
import store from './store'
function App() {
  return (
    <Provider store={store}>
      <BrowserRouter>
        <ConfigProvider locale={zhCN}>
          {renderRoutes(routes)}
        </ConfigProvider>
      </BrowserRouter>
    </Provider>
  );
}

export default App;

使用 Provider 包含整个 React 应用,所有容器组件都可以访问 store,而不必显式地传递它。只需要在渲染根组件时使用即可。通过context向子组件提供store

具体组件中使用 Store

Redux 的 React 绑定库是基于 容器组件和展示组件相分离 的开发思想

展示组件就是普通的 React 组件

容器组件使用 React Redux 的 connect(mapStateToProps, mapDispatchToProps) 方法来生成

  • mapStateToProps 表示要传入展示组件的值,这样展示组件就可以在 props 中读取这些值

  • mapDispatchToProps 表示要传入展示组件的 dispatch 方法,这样展示组件就可以在 props 中调用这些 dispatch 触发 Reducer

import { connect } from 'react-redux'
const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList
export function toggleTodo(type){
	return async (dispatch, getState) => {
		let { config } = getState()
		if(config.dict[type]){
			return
		}
		let { err, data } = await fetch({
			url: '/dict/listDict',
			params: {
				type
			}
		})
		if(!err){
			dispatch({
				type: 'UPDATE_DICT',
				key: type,
				data: data.data
			})
		}
	}
}

connect:用于创建容器组件,可以使容器组件访问到 Provider 组件通过 context 提供的 store ,并将 mapStateToPropsmapDispatchToProps返回的 state和dispatch 传递给UI组件。

# Vuex

Vuex 主要用于 Vue,和 Flux,Redux 的思想很类似

# Store

每一个 Vuex 里面有一个全局的 Store,包含着应用中的状态 State,Vuex 把 state 注入到了整个应用中,这样子组件能通过 this.$store 访问到 state

# Mutation

State 不能直接改,需要通过一个约定的方式,这个方式在 Vuex 里面叫做 mutation,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})
// 触发一个mutation
store.commit('increment')

注意:mutation 都是同步事务

mutation 有些类似 Redux 的 Reducer,但是 Vuex 不要求每次都搞一个新的 State,可以直接修改 State,这块儿又和 Flux 有些类似

# Action

对比Redux的中间件,Vuex 加入了 Action 这个东西来处理异步,Vuex的想法是把同步和异步拆分开,异步操作想咋搞咋搞,但是不要干扰了同步操作。View 通过 store.dispatch('increment') 来触发某个 Action,Action 里面不管执行多少异步操作,完事之后都通过 store.commit('increment') 来触发 mutation,一个 Action 里面可以触发多个 mutation。所以 Vuex 的Action 类似于一个灵活好用的中间件