# 共享状态思想
共享状态的思路就是:把组件之间需要共享的状态抽取出来,遵循特定的约定,统一来管理,让状态的变化可以预测。基于这个思想来分析下几种实现方式
# 全局
最简单的处理就是把状态存到一个外部变量里面,比如: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,必须经过一套流程
视图产生动作消息(Actions),将动作传递给调度器(Dispatcher)
调度器将动作消息发送给每一个数据中心
数据中心再将数据传递给视图
Flux 思想的好处:
视图组件变得很薄,只包含了渲染逻辑和触发 action 这两个职责
要理解一个 store 可能发生的状态变化,只需要看它所注册的 actions 回调就可以
任何状态的变化都必须通过 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
,并将 mapStateToProps
和 mapDispatchToProps
返回的 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 类似于一个灵活好用的中间件