[关闭]
@frank-shaw 2017-02-13T16:17:18.000000Z 字数 4972 阅读 1733

Redux初步理解

javaScript


Redux本质上是一个state Manager,专门负责管理应用中的state。一个应用中有很多组件需要记录状态,redux希望以state tree的方式来记录一个应用同一时刻的所有组件状态。要形成state tree,必须有合并(combine)的概念,下面的reducer会讲到。

个人认为它的time travel功能是非常强大的,可以回溯每一个状态。这个功能在调试中应该格外有用。当然,有如此功能也是基于它的工作原理的,让我们来看看其基本概念。

基本概念

actions

action表征的是每一个state包含的信息源,本质上只有action中携带的数据才是重要的。action在形式上是一个简单的Object,如:

  1. {type:'LIKE_ACTICLE', articleId:24}
  2. {type:'ADD_TODE', text:'Read the Redux docs.'}

action Object有一个要求:必须包含一个type属性,同时type属性的值建议大写(成为常量,同时在大型应用中,建议将这些常量单独放在一个文件中)。

action creator表示的是一类返回action的函数。值得注意的是,在同步情况下,action creator只允许返回一个action Object;在异步情况下,action creator除了返回action Object之外,还允许返回一个function。

reducers

actions仅仅描述了每一个state包含的信息,但是并没有没描述state之间是怎样变化的。这个工作就交给了reducers来做。它的基本格式是这样的:

  1. (previousState, action) => newState

reducers负责的就是state的变化的过程,由前一种state,添加了某一个action之后,转变为了一个新的state。它的这一作用就要求reducer函数必须是一个纯函数:无论何时,相同的输入将会得到相同的输出。同时,在书代码的时候,我们也必须切记这一点:reducer函数必须是纯函数。

同时,针对reducers有一个建议是:将data state 与 UI state尽量区分开,不要混杂在一起。一个简单的例子是这样的:

  1. //将data state 单独写成一函数
  2. function todos(state = [], action) {
  3. switch (action.type) {
  4. case ADD_TODO:
  5. //返回的是全新的state,通过ES6新形式
  6. return [
  7. ...state,
  8. {
  9. text: action.text,
  10. completed: false
  11. }
  12. ]
  13. case TOGGLE_TODO:
  14. return state.map((todo, index) => {
  15. if (index === action.index) {
  16. //返回的是全新的state,通过assign方法
  17. return Object.assign({}, todo, {
  18. completed: !todo.completed
  19. })
  20. }
  21. return todo
  22. })
  23. default:
  24. return state
  25. }
  26. }
  27. function todoApp(state = initialState, action) {
  28. switch (action.type) {
  29. case SET_VISIBILITY_FILTER:
  30. return Object.assign({}, state, {
  31. visibilityFilter: action.filter
  32. })
  33. case ADD_TODO:
  34. case TOGGLE_TODO:
  35. return Object.assign({}, state, {
  36. todos: todos(state.todos, action)
  37. })
  38. default:
  39. return state
  40. }
  41. }

多个reducers可以合并为一个reducer,通过函数combineReducers()来实现。如果合并的先后次序不同以及实行多次合并,那么就可以有了reducers tree的概念,进而可以发展为state tree的概念。将多个reducers转化为一个reducer,也是符合store的概念的,因为Redux要求只能有一个store。

reducer的使用原则:

store

上面讲到了reducer,讲述了其如何依据action的不同来改变state的。那么store就是讲所有的reducer合并在一起的工具。你可以将其看成一个将军,率领的是众多的reducer。

我们知道通过combineReducer()可以将多个reducer合并为单个reducer,那么直接使用createStore(combinedReducer),我们就可以获得包含了所有reducer的store。

store有如下功能:

  1. 获得当前的state:getState()
  2. 更新state: dispatch(action)
  3. 为store内部的reducer挂载监听事件:subscribe(listener)
  4. 如果想要解除监听事件,那么可以执行subscribe()所返回的函数,会将对应的监听事件解除

那么我会问:store中的dispatch(action)与reducer(previousState, action) => newState所实现的功能是一样的,那么它们之间有什么区别呢?让我们来开始尝试模拟一下createStore的内部实现吧(实际的实现比这复杂):

  1. const createStore = (reducer) => {
  2. let state;
  3. let listeners = [];
  4. const getState = () => state;
  5. const dispatch = (action) => {
  6. //dispatch内部实际上调用的就是reducer函数以作为更新state
  7. state = reducer(state,action);
  8. //每一次状态更新,都会触发所有的listeners做出相应,listener()内部可以调用getState()获取最新state
  9. listeners.forEach(listener => listener());
  10. };
  11. const subscribe = (listener) =>{
  12. //添加listener
  13. listeners.push(listener);
  14. //返回的函数作用是解除监听
  15. return function unsubscribe(){
  16. const index = listeners.index(listener);
  17. listener.splice(index,1);
  18. }
  19. }
  20. return {getState, dispatch, subscribe};
  21. }

工作流程

让我们来理一理redux的工作流程

调用store.dispatch(action)

我们已经明白action表示的含义。不管通过何种方式来调用(如:在界面上点击某个按钮,然后通过对应的函数调用),调用store.dispatch(action)之后,就有了接下来的第二步。

store调用其包含的reducer函数

store会传送两个参数给reducer(可能是简单的一个reducer,也可能是组合过后的combinedReducer):现阶段的state tree、上一步的action。我们知道reducer返回的是一个新的state,这个state也有可能是在combinedReducer包含的多个reducers所返回的states所组成的state tree。

挂载在store上的所有listeners对新的state tree做出响应

这是最后一步。我们得到了全新的state tree,但是依然需要将这些全新的state通过一定的处理之后才能够显示在界面上。这就是listener的作用。

针对异步

异步操作在前端操作中格外常见,redux如何解决异步带来的问题呢?前面讲到的几个基本概念里面都是以同步作为大背景的。

关注事物之间的不同,可以更好地了解它。与同步相比,异步的不同之处在于:

下面来看一个例子:

  1. //文件名称:action.js
  2. import fetch from 'isomorphic-fetch'
  3. export const REQUEST_POSTS = 'REQUEST_POSTS'
  4. function requestPosts(subreddit) {
  5. return {
  6. type: REQUEST_POSTS,
  7. subreddit
  8. }
  9. }
  10. export const RECEIVE_POSTS = 'RECEIVE_POSTS'
  11. function receivePosts(subreddit, json) {
  12. return {
  13. type: RECEIVE_POSTS,
  14. subreddit,
  15. posts: json.data.children.map(child => child.data),
  16. receivedAt: Date.now()
  17. }
  18. }
  19. // Meet our first thunk action creator!
  20. // Though its insides are different, you would use it just like any other action creator:
  21. // store.dispatch(fetchPosts('reactjs'))
  22. function fetchPosts(subreddit) {
  23. // Thunk middleware knows how to handle functions.
  24. // It passes the dispatch method as an argument to the function,
  25. // thus making it able to dispatch actions itself.
  26. return function (dispatch) {
  27. // First dispatch: the app state is updated to inform
  28. // that the API call is starting.
  29. dispatch(requestPosts(subreddit))
  30. // The function called by the thunk middleware can return a value,
  31. // that is passed on as the return value of the dispatch method.
  32. // In this case, we return a promise to wait for.
  33. // This is not required by thunk middleware, but it is convenient for us.
  34. return fetch(`https://www.reddit.com/r/${subreddit}.json`)
  35. .then(response => response.json())
  36. .then(json =>
  37. // We can dispatch many times!
  38. // Here, we update the app state with the results of the API call.
  39. dispatch(receivePosts(subreddit, json))
  40. )
  41. // In a real world app, you also want to
  42. // catch any error in the network call.
  43. }
  44. }

后记

这些只是基本的概念,需要在设计项目中不断学习,才能够更加深刻了解其内涵。


参考文档:
1.http://redux.js.org/docs/basics/
2.https://egghead.io/courses/getting-started-with-redux

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注