Flux
Redux是Flux思想的另一种实现方式。Flux是和React同时面世的。React用来替代jQuery,Flux用来替换Backbone.js等MVC框架。在MVC的世界里,React相当于V(view)的部分,只涉及页面的渲染。一旦涉及应用的数据管理部分,还是交给Model和Controller。不过,Flux并不是一个MVC框架,它用一种新的思路来管理数据。
【MVC】
MVC是业界广泛接受的一种前端应用框架类型,这种框架把应用分为三个部分:
Model(模型)
负责管理数据,大部分业务逻辑应该放在Model中。
View(视图)
负责渲染用户页面,应该避免在View中涉及业务逻辑。
Controller(控制器)
负责接受用户输入,根据用户输入调用相应的Model部分逻辑,把产生的数据结果交给View部分,让View渲染出必要的输出。
MVC框架提出的数据流很理想,用户请求先到达Controller,由Controller调用Model获得数据,然后把数据交给View。但是,在实际框架实现中,总是允许View和Model直接通信。
【Flux】
Facebook用Flux框架来替代原有的MVC框架,这种框架包含四个部分:
Dispatcher
负责动作分发,维持Store之间的依赖关系
Store
负责存储数据和处理数据相关逻辑
Action
驱动Dispatcher的javascript对象
View
视图负责显示用户界面
如果非要把Flux和MVC做一个对比。那么,Flux的Dispatcher
相当于MVC的Controller
,Flux的store
相当于MVC的model
,Flux的View
对应于MVC的View
,Action
对应给MVC框架的用户请求。
【优势】
在Flux中,Store只有get方法,没有set方法,根本不可能直接去修改其内部状态,View只能通过get方法获取Store的状态,无法直接去修改状态,如果View想要修改Store的状态,只能派发一个action对象给Dispatcher。
【不足】
1、Store之间依赖关系
在Flux的体系中,如果两个Store之间有逻辑依赖关系,就必须用上Dispatcher的waitFor函数。
2、难以进行服务器端渲染
3、Store混杂了逻辑和状态
Redux
Redux的含义是Reducer+Flux。Reducer是一个计算机科学中的通用概念。以Javascript为例,数组类型有reduce函数,接受的参数是一个reducer,reducer做的事情就是把数组所有元素依次做规约,对每个元素都调用一次参数reducer,通过reducer函数完成规约所有元素的功能。
Flux的基本原则是单向数据流,Redux在此基础上强调三个基本原则:
1、唯一数据源
2、保持状态只读
3、数据改变只通过纯函数完成
redux专注于状态管理,如果你的应用有以下场景,可以考虑使用 Redux。
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
核心概念包括:store
、state
、action
、reducer
。
【store】
store是保存数据的地方,redux提供createStore
函数来生成 Store。函数参数是后面要介绍的reducer。整个应用的state被储存在一棵object tree中,并且这个object tree只存在于唯一一个store中。
import { createStore } from 'redux'
const store = createStore(reducer)
【state】
state
是store
的某个时刻的快照,可以通过store.getState()
取得当前时刻的state
。state
是只读的,唯一改变state的方法就是触发action。
const state = store.getState()
【action】
action
用来改变state
。action
是一个对象,其中的type
属性是必须的,其他的属性一般用来设置改变state
需要的数据。state
的变化会导致view
的变化,但是用户接触不到state
,只能接触到view
。所以,state
的变化必须是view
导致的。action
就是view
发出的通知,表示state
应该要发生变化了。
const action = {
type: 'ADD_ONE',
num: 1
}
view要发送多少种消息,就会有多少种action
。如果都手写,会很麻烦,可以定义一个函数来生成action
,这个函数就叫action Creator
。
const ADD_TODO = '添加 TODO'
function addTodo (text) {
return {
type : ADD_TODO,
text
}
}
const action = addTodo('学习Redux')
store.dispatch()
是发出action的唯一方法
const action = {
type: 'ADD_ONE',
num: 1
}
store.dispatch(action)
【reducer】
reducer 是一个函数,它接受action
和当前state
作为参数,返回一个新的state
。
const reducer = (state = 10, action) => {
switch (action.type) {
case 'ADD_ONE':
return state + action.num;
default:
return state;
}
};
当store.dispatch
发送过来一个新的action
,store
就会自动调用reducer
,得到新的state。
【combineReducers】
传入store
时只能传入一个reducer
,多个reducer
需要进行合并。
let reducers = combineReducers ({
counter : counter,
todo : todo
})
combineReducers实现源码
export default function combineReducers(reducers) {
return function (state={},action) {
return Object.keys(reducers).reduce((newState,key) => {
newState[key]=reducers[key](state[key],action);
return newState;
},{});
}
}
【简单实例】
多余的概念不再介绍,下面用上面介绍的这四个核心概念实现一个简单的实例。
//第一步,创建action
const addOne = {
type: 'ADD',
num: 1
}
const addTwo = {
type: 'ADD',
num: 2
}
const square = {
type: 'SQUARE'
}
//第二步,创建reducer
let math = (state = 10, action) => {
switch (action.type) {
case ADD:
return state + action.num
case SQUARE:
return state * state
default:
return state
}
}
//第三步,创建store
import { createStore } from 'redux'
const store = createStore(math)
//第四步,测试,通过dispatch发出action,并通过getState()取得当前state值
console.log(store.getState()) //默认值为10
store.dispatch(addOne) //发起'+1'的action
console.log(store.getState()) //当前值为10+1=11
store.dispatch(square) //发起'乘方'的action
console.log(store.getState()) //当前值为11*11=121
store.dispatch(addTwo) //发起'+2'的action
console.log(store.getState()) //当前值为121+2=123
【redux目录】
1、一般地,将action.type
设置为常量,这样在书写错误时,会得到报错提示。
// action/ActionTypes.js
export const ADD = 'ADD'
export const SQUARE = 'SQUARE'
2、可以将addOne对象和addTwo对象
整合成add函数的形式
// action/math.js
import { ADD, SQUARE } from '../constants/ActionTypes'
export const add = num => ({ type: ADD, num })
export const square = { type: SQUARE }
3、根据action.type
的分类来拆分reducer
,最终通过combineReducers
方法将拆分的reducer
合并起来。上例中的action类型都是数字运算,无需拆分,只需进行如下变化
// reducer/math.js
import { ADD, SQUARE } from '../constants/ActionTypes'
const math = (state = 10, action) => {
switch (action.type) {
case ADD:
return state + action.num
case SQUARE:
return state * state
default:
return state
}
}
export default math
// reducer/index.js
import { combineReducers } from 'redux'
import math from './math'
const rootReducer = combineReducers({
math
})
export default rootReducer
4、将store存储到store/index.js文件中
// store/index.js
import { createStore } from 'redux'
import rootReducer from '../reducer'
export default createStore(rootReducer)
5、最终,根路径下的index.js内容如下所示
import store from './store'
import {add, square} from './action/math'
console.log(store.getState()) //默认值为10
store.dispatch(add(1)) //发起'+1'的action
console.log(store.getState()) //当前值为10+1=11
store.dispatch(square) //发起'乘方'的action
console.log(store.getState()) //当前值为11*11=121
store.dispatch(add(2)) //发起'+2'的action
console.log(store.getState()) //当前值为121+2=123
最终目录路径如下所示
UI层
前面的示例中,只是redux的状态改变,下面利用UI层来建立view
和state
的联系,将根目录下的index.js的内容更改如下。
import store from './store'
import React from 'react'
import ReactDOM from 'react-dom'
import { add, square } from './action/math'
ReactDOM.render(
{store.getState().math}
store.dispatch(add(1))} value="+1" />
store.dispatch(add(2))} value="+2" />
store.dispatch(square)} value="乘方" />
,
document.getElementById('root')
)
虽然可以显示数字,但是点击按钮时,却不能重新渲染页面。
【store.subscribe()】
接下来介绍store.subscribe()
方法了,该方法用来设置监听函数,一旦state
发生变化,就自动执行这个函数。该方法的返回值是一个函数,调用store.unsubscribe()
这个函数可以解除监听。
import store from './store'
import React from 'react'
import ReactDOM from 'react-dom'
import { add, square } from './action/math'
const render = () => ReactDOM.render(
{store.getState().math}
store.dispatch(add(1))} value="+1" />
store.dispatch(add(2))} value="+2" />
store.dispatch(square)} value="乘方" />
,
document.getElementById('root')
)
render()
store.subscribe(render)