@frank-shaw
2017-02-13T16:17:18.000000Z
字数 4972
阅读 1748
javaScript
Redux本质上是一个state Manager,专门负责管理应用中的state。一个应用中有很多组件需要记录状态,redux希望以state tree的方式来记录一个应用同一时刻的所有组件状态。要形成state tree,必须有合并(combine)的概念,下面的reducer会讲到。
个人认为它的time travel功能是非常强大的,可以回溯每一个状态。这个功能在调试中应该格外有用。当然,有如此功能也是基于它的工作原理的,让我们来看看其基本概念。
action表征的是每一个state包含的信息源,本质上只有action中携带的数据才是重要的。action在形式上是一个简单的Object,如:
{type:'LIKE_ACTICLE', articleId:24}
{type:'ADD_TODE', text:'Read the Redux docs.'}
action Object有一个要求:必须包含一个type属性,同时type属性的值建议大写(成为常量,同时在大型应用中,建议将这些常量单独放在一个文件中)。
action creator表示的是一类返回action的函数。值得注意的是,在同步情况下,action creator只允许返回一个action Object;在异步情况下,action creator除了返回action Object之外,还允许返回一个function。
actions仅仅描述了每一个state包含的信息,但是并没有没描述state之间是怎样变化的。这个工作就交给了reducers来做。它的基本格式是这样的:
(previousState, action) => newState
reducers负责的就是state的变化的过程,由前一种state,添加了某一个action之后,转变为了一个新的state。它的这一作用就要求reducer函数必须是一个纯函数:无论何时,相同的输入将会得到相同的输出。同时,在书代码的时候,我们也必须切记这一点:reducer函数必须是纯函数。
同时,针对reducers有一个建议是:将data state 与 UI state尽量区分开,不要混杂在一起。一个简单的例子是这样的:
//将data state 单独写成一函数
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
//返回的是全新的state,通过ES6新形式
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
//返回的是全新的state,通过assign方法
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
case ADD_TODO:
case TOGGLE_TODO:
return Object.assign({}, state, {
todos: todos(state.todos, action)
})
default:
return state
}
}
多个reducers可以合并为一个reducer,通过函数combineReducers()来实现。如果合并的先后次序不同以及实行多次合并,那么就可以有了reducers tree的概念,进而可以发展为state tree的概念。将多个reducers转化为一个reducer,也是符合store的概念的,因为Redux要求只能有一个store。
reducer的使用原则:
上面讲到了reducer,讲述了其如何依据action的不同来改变state的。那么store就是讲所有的reducer合并在一起的工具。你可以将其看成一个将军,率领的是众多的reducer。
我们知道通过combineReducer()可以将多个reducer合并为单个reducer,那么直接使用createStore(combinedReducer),我们就可以获得包含了所有reducer的store。
store有如下功能:
那么我会问:store中的dispatch(action)与reducer(previousState, action) => newState所实现的功能是一样的,那么它们之间有什么区别呢?让我们来开始尝试模拟一下createStore的内部实现吧(实际的实现比这复杂):
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
//dispatch内部实际上调用的就是reducer函数以作为更新state
state = reducer(state,action);
//每一次状态更新,都会触发所有的listeners做出相应,listener()内部可以调用getState()获取最新state
listeners.forEach(listener => listener());
};
const subscribe = (listener) =>{
//添加listener
listeners.push(listener);
//返回的函数作用是解除监听
return function unsubscribe(){
const index = listeners.index(listener);
listener.splice(index,1);
}
}
return {getState, dispatch, subscribe};
}
让我们来理一理redux的工作流程
我们已经明白action表示的含义。不管通过何种方式来调用(如:在界面上点击某个按钮,然后通过对应的函数调用),调用store.dispatch(action)之后,就有了接下来的第二步。
store会传送两个参数给reducer(可能是简单的一个reducer,也可能是组合过后的combinedReducer):现阶段的state tree、上一步的action。我们知道reducer返回的是一个新的state,这个state也有可能是在combinedReducer包含的多个reducers所返回的states所组成的state tree。
这是最后一步。我们得到了全新的state tree,但是依然需要将这些全新的state通过一定的处理之后才能够显示在界面上。这就是listener的作用。
异步操作在前端操作中格外常见,redux如何解决异步带来的问题呢?前面讲到的几个基本概念里面都是以同步作为大背景的。
关注事物之间的不同,可以更好地了解它。与同步相比,异步的不同之处在于:
下面来看一个例子:
//文件名称:action.js
import fetch from 'isomorphic-fetch'
export const REQUEST_POSTS = 'REQUEST_POSTS'
function requestPosts(subreddit) {
return {
type: REQUEST_POSTS,
subreddit
}
}
export const RECEIVE_POSTS = 'RECEIVE_POSTS'
function receivePosts(subreddit, json) {
return {
type: RECEIVE_POSTS,
subreddit,
posts: json.data.children.map(child => child.data),
receivedAt: Date.now()
}
}
// Meet our first thunk action creator!
// Though its insides are different, you would use it just like any other action creator:
// store.dispatch(fetchPosts('reactjs'))
function fetchPosts(subreddit) {
// Thunk middleware knows how to handle functions.
// It passes the dispatch method as an argument to the function,
// thus making it able to dispatch actions itself.
return function (dispatch) {
// First dispatch: the app state is updated to inform
// that the API call is starting.
dispatch(requestPosts(subreddit))
// The function called by the thunk middleware can return a value,
// that is passed on as the return value of the dispatch method.
// In this case, we return a promise to wait for.
// This is not required by thunk middleware, but it is convenient for us.
return fetch(`https://www.reddit.com/r/${subreddit}.json`)
.then(response => response.json())
.then(json =>
// We can dispatch many times!
// Here, we update the app state with the results of the API call.
dispatch(receivePosts(subreddit, json))
)
// In a real world app, you also want to
// catch any error in the network call.
}
}
这些只是基本的概念,需要在设计项目中不断学习,才能够更加深刻了解其内涵。
参考文档:
1.http://redux.js.org/docs/basics/
2.https://egghead.io/courses/getting-started-with-redux