常见数据流对比
目前主流方案
V = f(M)
f : Vue\React\Angular\...
数据管理
前端越来越大
数据越来越多
对数据的修改越来越多
数据存储结构
数据规范修改
Redux
核心思想:
1. web应用本质是一个状态机,视图和状态一一对应
2. 所有状态存在一个对象中
Redux
store:
存储全局数据
整个应用只能有一个store
通过redux提供的createStore方法生成
State:
store在某个时间点的快照
store.getState()可以获取
state与view必须一一对应
Action:
state无法直接被改变,action表示state会发生的变化
改变state的方法就是使用action
Redux
Action Creator:
每一种变化都得有一个对应的action,因此可以通过creator生成action
store dispatch:
action规定了会发生的变化或者动作,dispatch是触发action
reducer:
store收到action时,需产生一个对应且不同的state对象,view会随之变化。
reducer接收当前state和action,返回一个全新的state
reducer就是createStore的参数,可以生成初始store
Redux
纯函数:
reducer是纯函数,无法修改传入的state,每次会生成全新的state
subscribe:
监听函数。当state变化时会自动触发subscribe。对于很多MVVM框架,可以将render或者react的setState放在subscribe函数中,实现自动刷新。
Demo
// index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './containers/App'
import todoApp from './reducers'
let store = createStore(todoApp)
let rootElement = document.getElementById('root')
render(
<Provider store={store}>
<App />
</Provider>,
rootElement
)
// components
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
// actions.js
export const ADD_TODO = 'ADD_TODO';
export const COMPLETE_TODO = 'COMPLETE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
};
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function completeTodo(index) {
return { type: COMPLETE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
Demo
// reducer.js
import { combineReducers } from 'redux'
import { ADD_TODO, COMPLETE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return ***
case COMPLETE_TODO:
return ***
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Demo
// containers/App.js 容器js
import ****
class App extends Component {
render() {
const { dispatch, visibleTodos, visibilityFilter } = this.props
return (
<div>
<AddTodo
onAddClick={text =>
dispatch(addTodo(text))
} />
<TodoList
todos={visibleTodos}
onTodoClick={index =>
dispatch(completeTodo(index))
} />
<Footer
filter={visibilityFilter}
onFilterChange={nextFilter =>
dispatch(setVisibilityFilter(nextFilter))
} />
</div>
)
}
}
App.propTypes = {}
function selectTodos(todos, filter) {
****
}
// Which props do we want to inject, given the global state?
// Note: use https://github.com/faassen/reselect for better performance.
function select(state) {
return {
visibleTodos: selectTodos(state.todos, state.visibilityFilter),
visibilityFilter: state.visibilityFilter
}
}
// 注入 dispatch 和 state
export default connect(select)(App)
基本原理
用户通过dispatch发出action
dispatch(action)
store自动调用reducer,传入action和当前state,生成全新state
let nextState = reducer(state, action)
state一旦变化,调用监听函数
store.subscribe(fn)
redux-thunk
加入了async、await
onSubmit = username => {
this.props.dispatch(searchUsers(username, 1))
}
export const searchUsers = (username, page) => {
return async dispatch => {
dispatch({
type: CONST.FETCH_GITHUB_SEARCH_USER_LOADING
})
try {
const response = await fetch(`https://api.github.com/search/users?q=${username}&page=${page}`)
let data = await response.json()
dispatch({
type: CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS,
payload: data
})
}catch(e) {
dispatch({
type: CONST.FETCH_GITHUB_SEARCH_USER_FAILURE,
error: "No This User"
})
}
}
}
redux-thunk
view
dispatch
function
action object
reducer
dispatch
dispatch
action object
update
store
state
dispatch传参:
Action Object,reducer会更新数据
function,函数内进行各种操作(可以用await实现异步),最后dispatch一个Action,进入上一情况
redux-saga
简化了 action creator
onSubmit = username => {
this.props.dispatch(searchUsers(username, 1))
}
export const searchUsers = function*(action) {
let {username,page} = action.payload
yield put({
type: CONST.FETCH_GITHUB_SEARCH_USER_LOADING
})
try {
let response = yield call(axios.get, `https://api.github.com/search/users?q=${username}&page=${page}`)
let data = response.data
yield put({
type: CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS,
payload: data
})
}catch(e) {
yield put({
type: CONST.FETCH_GITHUB_SEARCH_USER_FAILURE,
error: "No This User"
})
}
}
redux-saga
view
Action Object
dispatch
reducer
not match
sagas
match
Action Object
dispatch
store
state
update
update
dispatch一个Action Object:
Action被sagas监听,进入sagas中处理,在sagas中dispatch一个Action Object,reducer更新数据
Action没有被sagas监听,reducer更新数据
二者区别
1. 原理不同
thunks在dispatch时才会创建。sagas是generator,应用启动时便被创建
2. 操作
sagas是generator,因此可以cancel等操作,async是无法做到的
3. 测试
sagas的可中断性,使其相对于其他函数更容易测试
4. action语义
业务逻辑放在sagas相比于业务逻辑放在thunks(async function action),action简洁且无副作用,保持了原意
redux-observable
observable:
在rxJS中,observable => asynchronous immutable array
通俗点 时间轴 + 数组 = observable
emit event sometime
push to array
notify subscriber
redux-observable
const search$ = Observable.fromEvent(input, 'input')
.map(e => e.target.value)
.filter(value => value.length >= 1)
.throttleTime(100)
.distinctUntilChanged()
.switchMap(term => Observable.fromPromise(wikiIt(term)))
.subscribe(
x => renderSearchResult(x),
err => console.error(err)
)
假设输入了 5 次
每次输入的值一次为:a, ab, c, d, c
并且第 3 次输入的 c 和第 4 次的 d 的时间间隔少于100ms
---a--ab---c-d-----c---|-->
---i--i---i-i-----i---|--> (input)
---a--ab---c-d-----c---|-->
---a--ab---c-------c---|-->
---a--ab---c----------|-->
---x--y---z----------|-->
map
filter
throttleTime
change
map
Marble diagrams
在redux中使用observable
onSubmit = username => {
this.props.dispatch(searchUsers(username, 1))
}
redux-observable
// actions.js
export const searchUsers = (username, page) => {
return {
type: CONST.FETCH_GITHUB_SEARCH_USER_LOADING,
payload: {
username,
page
}
}
}
export const searchUsersSuccess = list => {
return {
type: CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS,
payload: {
total_count: list.total_count,
items: list.items
}
}
}
export const searchUsersFailure = () => {
return {
type: CONST.FETCH_GITHUB_SEARCH_USER_FAILURE,
error: "No This User"
}
}
在redux中使用observable
redux-observable
// epics.js
const searchUsers$ = (username, page) => Observable.fromPromise(request(`https://api.github.com/search/users?q=${username}&page=${page}`)).delay(2000)
export const searchUsersEpic = action$ => {
return action$
.ofType(CONST.FETCH_GITHUB_SEARCH_USER_LOADING)
.mergeMap(e => {
return searchUsers$(e.payload.username, e.payload.page)
.map(list => actions.searchUsersSuccess(list))
.catch(error => actions.searchUsersFailure())
})
}
// reducers
export const usersReducer = (state={
loading: false,
total: 0,
data: [],
error: ''
}, action) => {
switch(action.type) {
case CONST.FETCH_GITHUB_SEARCH_USER_LOADING:
return {
...state,
loading: true
}
case CONST.FETCH_GITHUB_SEARCH_USER_FAILURE:
return {
loading: false,
error: action.error
}
case CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS:
return {
loading: false,
total: action.payload.total_count,
data: action.payload.items
}
default:
return state
}
}
redux-observable
An Epic is the core primitive of redux-observable
Epic is a function which takes a stream of actions and returns a stream of actions.
Actions in, actions out.
View
Action Object
Epics
Action Object
Reducer
Store
State
dispatch
match
dispatch
not match
update
update
redux-saga VS redux-observable
saga:
Apply a process manager for async, timed, or other non-pure effects so that you can keep application flow logic clean.
redux-observable:
Applying RxJS observable flow to Redux, and therefore handling async actions in middleware. Actions in -> Actions out styled workflow.
mobx
transparently applying functional reactive programming

Core: observable
mobx
Observable state
class Todo {
id = Math.random();
@observable title = "";
@observable finished = false;
}
Computed values
class TodoList {
@observable todos = [];
@computed get unfinishedTodoCount() {
return this.todos.filter(todo => !todo.finished).length;
}
}
mobx
Reactions
产生副作用
响应式与命令式之间的桥梁
原理
observable 包装原生值,get 钩子收集依赖,set 钩子触发改变。
autorun 包装观察者函数,在包装时会提前执行一次,并且触发其中涉及到的 observable 值的 get 钩子收集依赖,将当前的观察者函数对应到每个 observable 值上。(同步)
mobx
// index.jsx
@inject('usersStore', 'followersStore', 'followingsStore')
@observer
export default
class App extends React.Component {
constructor(props) {
this.state = {
username: '',
}
}
onSubmit = username => {
this.props.usersStore.searchUsers(username, 1)
}
render() {
return(
/* */
<input value={this.props.usersStore.value}/>
<button onClick={()=>{this.onSubmit(this.state.username)}}>search</button>
)
}
}
mobx
// store.js
class UsersStore {
@observable data = []
@observable loading = false
@observable total = 0
@observable error = ''
@action
async searchUsers(username, page) {
this.loading = true
await delay(2000)
try {
const response = await fetch(`https://api.github.com/search/users?q=${username}&page=${page}`)
let resData = await response.json()
// 异步 action 之后,再次修改状态需要动作:
runInAction(() => {
this.loading = false
this.data = resData.items
this.total = resData.total_count
})
}catch(e) {
runInAction(() => {
this.loading = false
this.error = 'No This User'
})
}
}
}
export const usersStore = new UsersStore
mobx
Events
Actions
@action
State
@observable
Computed values
@computed
Reactions
@observer
Modify
update
trigger
redux VS mobx

redux VS mobx
学习成本
MobX 是 magic 的,通过 computed value、observer class、autorun function 对于 View 层的输入逻辑进行了抽象,我们只需要在 View 层对相应的 observale 数据进行访问即可完成自动变更。
Redux 需要考虑如何规范化你的 state,如何使用可记忆的 selector 函数(可记忆的selector来缓存变化),如何更好地处理异步等等。
redux VS mobx
代码量
MobX 内置抽象,代码简洁
Redux 对于每一个变更状态的操作,需要些 action,然后 View 调用 action , 根据 action,写出对应的 reducer。
redux VS mobx
可调试性、可预测性和可测试性
MobX 内部复杂,可以直接在 View 层对 observable 对象进行操作。 useStrict 限制下,只能 action 来修改 state
Redux 是简洁、纯净的,能够进行时间回溯,从 state -> view -> action -> reducer 形成单向闭环,使可调试性和可预测性较好,而对于 action 和 reducer 均可单独测试。
redux VS mobx
模块化
MobX 的 store 是分散的,不同的 store 可以封装对应的业务逻辑。但应用复杂之后,不同 store 之间的协调、分享、相互引用难以维护
Redux 是单一 store,全局共享,无模块化
redux VS mobx
可扩展性
MobX 的 store 分散,改变可以发生在各个地方,无法保证严格顺序执行。
Redux 中所有的修改都被集中处理,严格按照顺序执行
谢谢
数据流对比
By Joson Chen
数据流对比
- 517