摘至: please call me HR
redux 是fb提出的一个面向所有库或者框架的MVVM的一种思维. 相当于将react 专有的flux 提取出来。 作为一个专业库来做。这样的相当于使用react+flux构造的MVVM, 可以使用原生js + redux来写, 限制性减小了很多.
由于是借用flux的, 所以很多原理和flux 很相似. 官方提出的有三点:
Single source of truth: 单一数据源,其实就是一个Obj或者Array
State is read-only: 不能手动改变数据源
Changes are made with pure functions: 改变数据源,只能通过reducer来改变
redux和flux的差别
说实话, 两者看底层代码是完全不一样的. 但宏观的基本原理来说,并没有多大差别.
这是flux的:
这是redux:
觉得并没有什么太大的区别.
接下来, 我们来细致的看一看redux到底有什么用.
入门redux
我们这里利用redux来简单写一个小组件。这里,我们不依赖任何第三方库,自己手动写.
首先HTML内容为:
Clicked: 0 times
接着,就是redux内容:
// 先写一个Reducer
function counter(state, action) {
if (typeof state === 'undefined') {
return 0
}
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
// 并注册该Reducer
var store = Redux.createStore(counter)
由于没有使用react, 这里的render方法,需要我们by hand进行手写.
// 改变节点的textNode内容
var valueEl = document.getElementById('value')
function render() {
valueEl.innerHTML = store.getState().toString()
}
render()
// 在store里面,绑定render渲染方法. 但改变store的时候,会触发一次render
store.subscribe(render)
最后,就是书写一下Event即可
document.getElementById('increment').addEventListener('click', function () {
// 绑定Action, 触发dispatch操作
store.dispatch({ type: 'INCREMENT' })
})
// 后面还有一些绑定方法,我这里就不写了.
整个代码是在Counter里. 根据上面的demo,我们可以大概的了解到. store是redux的core part. 一共有三个基本的方法, 搭建起redux的骨架.
Redux.createStore(Reducer): 创建store对象
store.subscribe(render): 绑定渲染方法
store.dispatch({type}): 触发Reducer,进行渲染.
这里,顺便说一下redux中Store,Reducer,Action的意义:
Store: 单一数据源, 用来存放数据,并且和DOM同步
Reducer: 注册对应Action的函数,用来显示改变store
Action: 触发dispatch的行为. 比如: Event 绑定内容
上面介绍的是简单使用redux进行纯开发. 接着,我们来简单看如何结合react进行MVVM开发.
react+redux
我们都知道,react里面有一个最特殊的属性就是state. 他通过自身state的改变,来导致重新渲染发生.this.setState({xxx:xxx}). 但是 redux里面也有一个store, 当然,一山不能容二虎. 那只能二者选其一(当然选redux). 那这样的话,我们就不能使用this.setState进行手动更新. 那么就需要考虑能否使用redux进行代替.
方法是有的,即,使用上面替代的store.subscribe(render).通过props将dispatch的方法传入,进行Reducer的更改.
PS: 这里主要参考todos里的代码
easy react+redux demo
先来看一下,在渲染层中的App内容.
const store = createStore(counter)
const rootEl = document.getElementById('root')
// 将dispatch的函数通过props向子组件进行传递
function render() {
ReactDOM.render(
store.dispatch({ type: 'INCREMENT' })}
onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
/>,
rootEl
)
}
render()
// 将render 缓存,以备下一次进行刷新.
store.subscribe(render)
接着在组件中,调用该方法:
render() {
// 获取props里面,传递来的参数
const { value, onIncrement, onDecrement } = this.props
return (
Clicked: {value} times
{' '}
{' '}
{' '}
{' '}
)
}
reducer内容还是一样:
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
当react和redux结合在一起时, react中,内部状态管理的机制,就需要显示的交给redux来做.
reducer 讲解
reducer 就是一个函数,关键在里面接受的参数
// 第一个参数是 createStore内部解决的. 第二个参数action是你通过dispatch({})中传递的Obj.
// like: dispatch({type: 'INCREMENT'})
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
redux + react高阶应用
上面那个简单的demo,我们意会一下就ok了. 实际生产环境中,并不是这样写代码的. 因为在结合react的时候,必定会涉及到多个component. 但,由于结合redux时,react把state交给redux来管理, 为了满足这个需求,redux提出了container的概念.
我们参考todos的源码, 来细致的看一下redux 高阶应用.
首先看他的index.js:
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
// 其余的剩余...
// 将Reducer 创建为Store对象
let store = createStore(todoApp)
// 使用react-redux 提出的Provider来完成
render(
,
document.getElementById('root')
)
这里就简单说一下,Provider的作用. 通过传入store,相当于就酱render方法和store绑定在一起来. 不需要我们像上面一样,显示的将render和store经由subscribe绑定在一起.
看一下Reducer里面的代码:
import { combineReducers } from 'redux'
// 剩余...
// 将两个reducer combine在一起. 将白了就是按顺序调用. 相当于路由注册的意思
const todoApp = combineReducers({
todos,
visibilityFilter
})
这里顺带提一下combineReducers的用法.
combineReducers
该方法,将多个reducer结合在一起,组成一个obj,用来监听dispatch触发的改变.
// 艹,感觉也没什么说的. 后面再深入源码看看吧
const todoApp = combineReducers({
reducer1,
reducer2,
reducer3
})
每个reducer里面的内容,我就不过多赘述了.简单的看一下:
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
]
...
default:
return state
}
}
回到上面的index.js,我们来看一下App.js里面做了什么.
// 其实也没什么
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'
const App = () => (
)
export default App
这里, 再次提醒大家一个概念.container. 看一下上面那个图。
说道 container 就不得不提一下 connect 方法。
connect
connect 是 react-redux提出的. 由于 react将state交给了外部管理. 所以,connect 这个就需要起到一个pipe的效果.将components 变装为container.通常connect,需要在Provider包裹的组件中使用.如上面
connect能做的事情有两个,一个是传递dispatch,一个是传递store.
// 只注入dispatch,不监听store
export default connect()(TodoApp);
// 正常一点的写法就是
let AddTodo = ({ dispatch }) => {
}
AddTodo = connect()(AddTodo)
还有一个是:
// 注入state.用来监听全局store的变化
// 官方给出的建议是,最小片断化. 即每个container 只捕获属于他一部分的state,这样能保证性能的最优
function mapStateToProps(state) {
return { todos: state.todos };
}
export default connect(mapStateToProps)(TodoApp);
然后是两个混合传入:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
// 返回和redux store连接的新组件
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
事实上, connect可以接受4个参数:
// 基本格式为:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
[mapStateToProps(state, [ownProps]): stateProps] (Function): 这个方法相当于就是用来传入state的.
-
state: 当前store的内容。
-
ownProps: 为当前组件接受到的Props. 相当于就是把react的props给放到这个参数里面
-
返回值: 必须是一个对象.他会与props结合.
该函数的用法也没啥说的. 就注意一点, 如果store发生改变,则会触发该函数
第二个是:
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): 该函数通常使用来注册,触发函数一块. 该方法是将compnents和container连接的关键方法.因为他的dispatch可以触发store的改动,从而使状态整体更新. dispatch就是触发函数,onwProps就是组件传递的属性.
-
返回值: 返回通过dispatch绑定的对象集合
还有两个参数,这里就不做过多的解释了. 友情参考:connect 解析
在Todos里面,使用到比较复杂的connect 代码为:
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = (dispatch) => {
return {
onTodoClick: (id) => {
dispatch(toggleTodo(id))
}
}
}
// 返回和redux store连接的新组件
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
看一下,TodoList实际接受的情况:
const TodoList = ({ todos, onTodoClick }) => (
//...
)
这应该很清楚了. 你通过connect 返回的对象,最后会通过Object.assign()进行合并. 所以,你对象的名字需要取一样的。这点需要注意.
上面写了这么多,逻辑可能会有点乱,我们大致来理一理.
react+redux 逻辑关系
开始的时候,我们看了一下简单的redux demo. 发现,里面并没有出现Action的行为,大体只看到的reducer. 所以,在简单demo中,我们的整体架构为:
这里,我将action给故意隐藏了. 或者说实在一点,你就可以把dispatch当成是action. 这样理解也没问题.
如果涉及到比较复杂的,那么整个流程图,我这里就不用上面四大天王的形式了.我们深入到方法进行分析.