-
Notifications
You must be signed in to change notification settings - Fork 1
Description
记得在16年初识Redux时,笔者是被其搞得晕头转向,总觉得有点把简单的事情复杂化,而且对于Action、Reducer等,它们是怎样连接state并触发视图更新的,也是一脸懵逼。
近来工作没有那么忙,饶有兴致地抽时间来研究了一下Redux的源码,希望可以帮助大家弄懂Redux原理,以便更加轻松地投入到实际业务开发中。
本系列文章一共分为两篇,阅读完本系列文章,你将会对Redux的思想和使用有深刻的理解。示例篇将通过一个Redux示例页面来帮助大家了解其使用流程,帮助大家先理解Redux的思想,为后续自己动手写一个Redux打下基础;原理篇将会带大家一步一步实现一个Redux,同时还会附上详细的讲解以及笔者个人的一些思考。
如果已经熟悉Redux的使用,可以直接跳过本文,阅读 Redux其实很简单(原理篇)。
使用前提
Redux是一个状态管理容器,在很多情况下,我们并不需要它。但是假如在页面中,有许多状态数据需要在不同组件中共享,状态管理非常困难时,可以考虑使用Redux,它可以帮助我们组织代码结构,方便进行状态管理。通常情况下spa页面比较常用,搭配前端路由管理一起使用。
示例页面
store
核心方法:createStore(reducer, [initialState], enhancer)
// index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from '@/common/redux'
import logger from '@/common/redux-logger'
import thunk from '@/common/redux-thunk'
import reducers from './reducers'
import Counter from './components/Counter'
const store = createStore(reducers, { counter: 0 }, applyMiddleware(thunk, logger))
const renderWithStore = () => render(
<Counter
data={store.getState()}
dispatch={store.dispatch}
/>,
document.getElementById('root')
)
renderWithStore()
store.subscribe(renderWithStore)
首先我们通过核心方法createStore得到渲染视图必要的API【getState, dispatch, subscribe】,再将渲染函数注册到监听流当中,每当dispatch触发时,所有注册到监听流的函数都会执行,这也是为什么状态改变时视图也会更新的关键。
举个例子,上述代码中最后再加一行
store.subscribe(() => {
console.info('test')
})
那么dispatch触发时,除了视图重新渲染,还会再控制台打印出test。
reducers
// reducers/counter.js
export default (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
case 'INCREMENT_BY':
return state + action.count
default:
return state
}
}
然后我们通过combineReducers将多个reducer合并成一个,此时state是一个对象,每一个reducer的状态对应state的一个键值。
// reducers/index.js
import { combineReducers } from '@/common/redux'
import counter from './counter'
export default combineReducers({
counter
})
到目前为止,我们已经将状态和视图通过reducer连接起来了。接下来我们将通过action来触发dispatch,在这个过程中,dispatch会触发reducer来得到最新的state,然后执行监听流当中的函数来重新渲染视图。
action
// actions/counter.js
export function increment () {
return {
type: 'INCREMENT'
}
}
export function decrement () {
return {
type: 'DECREMENT'
}
}
export function incrementAsync () {
return dispatch => {
setTimeout(() => {
dispatch(increment())
}, 1000)
}
}
export function incrementBy (count = 0) {
return {
type: 'INCREMENT_BY',
count
}
}
// actions/index.js
import * as counterCreator from './counter'
export {
counterCreator
}
action定义好了之后,我们就可以在视图上触发action来改变state的值,更新视图。我们通过action.type
来匹配reducer中对应的类型来得到新的state值,值得注意的是,原始的Redux只支持返回一个包含type字段的对象,通过redux-thunk
中间件,我们可以进行异步操作,比如说请求接口,拿到数据后触发dispatch。
component
// components/Counter.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { bindActionCreators } from '@/common/redux'
import { counterCreator } from '../actions'
export default class Counter extends Component {
constructor (props) {
super(props)
this.boundActionCreators = bindActionCreators(counterCreator, props.dispatch)
}
static propTypes = {
data: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
}
increment () {
this.boundActionCreators.increment()
}
decrement () {
this.boundActionCreators.decrement()
}
incrementAsync () {
this.boundActionCreators.incrementAsync()
}
incrementBy () {
this.boundActionCreators.incrementBy(10)
}
incrementIfOdd () {
if (this.props.data.value1 % 2 !== 0) {
this.boundActionCreators.increment()
}
}
render () {
const { data } = this.props
return (
<div>
<p>
Counter: {data.counter} times
{' '}
<button onClick={this.increment.bind(this)}>
+
</button>
{' '}
<button onClick={this.decrement.bind(this)}>
-
</button>
{' '}
<button onClick={this.incrementIfOdd.bind(this)}>
Increment if odd
</button>
{' '}
<button onClick={this.incrementAsync.bind(this)}>
Increment async
</button>
{' '}
<button onClick={this.incrementBy.bind(this)}>
Increment by
</button>
</p>
</div>
)
}
}
我们通过bindActionCreators
将dispatch和action绑定在一起,然后直接调用就可以了。当然也可以不用绑定,那就这样写:
this.props.dispatch(counterCreator.increment())
bindActionCreators
的作用就是省略这一步,将两者系在一起直接调用。
文末总结
至此,一个完整的示例页面已经编写完成。在写的过程中,笔者已经将各个API的作用及其联系做了简单的说明,方便大家了解其执行流程和思想,那么在接下来的一篇文章中,笔者会带大家一步一步编写一个Redux,相信大家看完之后会对Redux有一个深刻的认识。