Redux入门

Redux入门

Redux自述


Redux 是 JavaScript 状态容器,提供可预测化的状态管理。 (如果你需要一个 WordPress 框架,请查看 Redux Framework。)
可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。不仅于此,它还提供 超爽的开发体验,比如有一个时间旅行调试器可以编辑后实时预览。
Redux 除了和 React 一起用外,还支持其它界面库。 它体小精悍(只有2kB,包括依赖)。

Redux对于JavaScript应用而言是一个可预测状态的容器。换言之,它是一个应用数据流框架,而不是传统的像underscore.js或者AngularJs那样的库或者框架。
在Redux中,所有的数据(比如state)被保存在一个称为store的容器中(在一个应用程序中只有一个)。store保存了所有的对象的状态,任何UI组件都可以直接从strore中获取指定对象的状态。如果要从本地或远程更改状态,需要分发一个actionaction通过dispatch函数分发给storestore接收到action,将它给相关的reducerreducer是一个纯函数,返回一个新的state

为什么需要Redux?


React用stateprops来进行组件的渲染,但是在工程庞大的项目里,这种做法不仅会使代码变得庞大而且不易于维护。Redux以 store为单一数据源,便于各个组件之间数据的传递。

Redux配置。


npm install redux --save   //安装最新版本的Redux
npm install react-redux --save   // React绑定库
npm install redux-devtools --save-dev   // Redux开发者工具

Redux基本概念


- State

state是整个应用的数据,本质上是一个普通对象。
state决定了整个应用的组件如何渲染,渲染的结果是什么。
并不是所有的数据都保存在state当中,有些属于组件本身的数据完全可以留给组件本身去维护。

- Action

action就是把数据从应用传到store的有效载荷。它是state的唯一来源,一般可以通过store.dispatch(action)将action传到store。
action本质上是JS普通对象,action内
必须
有一个字符串type来表示要执行的动作。当规模越来越大的时候,可以令建一个文件actionTypes创建action中的type字符串。
示例,一个action:

{
	type:'ADD_TODO',
	text:'The world is beautiful.'
}

除了type字段外,action对象的结构完全由你自己决定。
有时,我们还需要添加一个action index来表示用户完成任务的动作***。
我们应该尽量少在action中传递数据。传递index就比把整个任务对象传过去要好。
action创建函数:
在大的项目中,action一个一个地手动创建不利于管理。一般会写好action的创建函数,即actionCreators.js。调用actionCreators会返回对应的action。
触发action:
创建好action后,如何触发这些action呢。使用reduxAPI中的dispatch函数,store.dispatch(action)

- Reducer

action只是描述了有事情发生这一事实,并没有描述应用如何更新state。reducer指定了应用状态的变化和如何响应action并发送到store的。
reducer是state最终格式的确定。它是一个纯函数,也就是说,只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,只是单纯执行计算。
reducer就是一个纯函数,接收旧的state和action,返回新的state。
比如,一个加减的reducer:

import {INCREMENT_COUNTER,DECREMENT_COUNTER} from '../actions';

export default function counter(state = 0, action) {
    switch (action.type){
        case INCREMENT_COUNTER:
            return state+1;
        case DECREMENT_COUNTER:
            return state-1;
        default:
            return state;
    }
}

对于大的项目,我们是需要将reducer给拆分的,最后通过redux提供的combineReducers方法组合在一起,就如上面的reducer

const rootReducer = combineReducers({
    counter
});
export default rootReducer;

每个reducer只负责全局state中它负责的那一部分。每个reducerstate参数都不同,分别对应它管理的那部分state数据。

- store

store就是把actionreducer联系在一起的对象,具有如下职责:

  • 维持应用的state
  • 提供**getState()**方法获取 state
  • 提供**dispatch(action)**方法更新 state
  • 通过**subscribe(listener)**注册监听器
  • 通过**subscribe(listener)**返回的函数注销监听器

Redux应用只有一个单一的store。当需要拆分数据处理逻辑时,应该使用reducer组合而不是建立多个store。
假设我们已经用combineReducers()将多个reducer合并为一个todoApp,先在我们将其导入,并传递createStore()

import { createStore } from 'redux';
import todoApp from './reducers';
let store = createStore(todoApp);

store的创建通过reduxcreateStore方法创建,这个方法还需要传入reducer,很容易理解:毕竟我需要dispatch一个action来改变state嘛。 应用一般会有一个初始化的state,所以可选为第二个参数,这个参数通常是有服务端提供的,传说中的Universal渲染。 第三个参数一般是需要使用的中间件,通过applyMiddleware传入。
actionstoreactionCreatorreducer之间的关系就是如下了:
Redux入门

搭配React


这里需要强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
尽管如此,Redux 还是和 ReactDeku 这类库搭配起来用最好,因为这类库允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。

- 安装React Redux

Redux默认并不包含React绑定库,需要单独安装

npm install react-redux --save

- UI组件和容器组件

React-redux将组件分成了两大类:UI组件和容器组件。简单来说,UI组件用来描述如何展现,容器组件是描述如何运行(数据获取,状态更新)。

- connect()

容器组件需要connect函数,connect函数:mapStateToPropsmapDispatchToProps
mapStateToProps 这个函数来指定如何把当前 Redux store state 映射到展示组件的 props 中。
mapDispatchToProps 方法接收 dispatch() 方法并返回期望注入到展示组件的 props 中的回调方法。

-传入store

所有容器组件都可以访问Redux store,所以可以手动监听它。
使用指定的React Redux组件****来让所有的组件都可以访问store。

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

React-Redux代码规范


- 创建应用store

src目录下,创建store文件夹,在其下建立index.js(创建store)和reducer.js(整理reducer组合)

  • reducer.js:
//被拆分的各个reducer
import { reducer as headerReducer} from '../common/Header/store';
import { reducer as homeReducer} from '../pages/home/store';
import { reducer as detailReducer } from '../pages/detail/store';
import { reducer as loginReducer } from '../pages/login/store';

const reducer =combineReducers ({
  header:headerReducer,
  home:homeReducer,
  detail:detailReducer,
  login:loginReducer
});

export default reducer;
  • index.js:
import { createStore ,applyMiddleware } from 'redux';
import thunk from 'redux-thunk';    // 使用redux中间件redux-thunk
import reducer from './reducer';

const store = createStore(reducer,applyMiddleware(thunk));

export default store;

- 传入store

在React路由管理文件中修改,比如src下的App.js

  • App.js:
import React, { Component } from 'react';
import { BrowserRouter,Route } from 'react-router-dom';
import Header from './common/Header';
import Login from './pages/login';
import store from './store/index.js';    // 引入store库
import { Provider } from 'react-redux';   // 引入Provider

class App extends Component {
  render() {
    return (
      <Provider store={store}>     // 被<Provider>包含的组件都可以访问store中的数据
          <BrowserRouter>
            <div>
              <Header />
              <Route path='/login' exact component={Login} ></Route>
            </div>
          </BrowserRouter>
      </Provider>
    );
  }
}

export default App;

- 被拆分的reducer

在需要使用reducer的组件文件夹下创建store文件夹,创建index.jsreducer.jsactionTypes.jsactionCreators.js四个文件。
例如Login组件:

  • actionTypes.js:(用于存放各种action.type)
export const CHANGE_LOGIN = 'login/CHANGE_LOGIN';
export const CHANGE_LOGOUT = 'login/CHANGE_LOGOUT';
  • actionCreators.js:(action的创建函数)
import * as actionTypes from './actionTypes';

export const logout = () => ({
    type:actionTypes.CHANGE_LOGOUT,
    value:false
})
  • reducer.js:
import * as actionTypes from './actionTypes';

const defaultState = {
	login:true
}

export default ( state = defaultState,action ) => {
  if(action.type === actionTypes.CHANGE_LOGOUT){
    return state.set('login',action.value)                 // 需要注意的是state不能直接改变,只能返回一个新的state
  }
  return state;
}
  • index.js:
import reducer from './reducer'
import * as actionCreators from './actionCreators';
import * as actionTypes from './actionTypes';
export { reducer,actionCreators,actionTypes };    // 将reducer,actionTypes,actionCreators归总导出

- 使用store

就如上述例子的Login组件。在login文件夹下建index.js来写组件。

import { connect } from 'react-redux';    // 引入connect函数
import { actionCreators } from './store';