概念
React-Redux
将所有组件分成两大类:UI 组件(presentational component)
和 容器组件(container component)
。
UI 组件
- 只负责 UI 的呈现,不带有任何业务逻辑
- 无状态(即内部没有
state
)- 所有数据都来自
props
- 不使用任何 Redux 的 API
容器组件
- 负责管理数据和业务逻辑,不负责 UI 的呈现
- 带有内部状态
- 使用 Redux 的 API
方法:
connect
用于从 UI 组件生成容器组件
import React from 'react'
import { connect } from 'react-redux'
class TodoList extends React.Component {
constructor (props) {
super(props);
// props.todos
}
// ...TODO
}
const mapStateToProps = (state,ownProps) => {
return {
todos: state.stateTodos
}
}
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
connect
方法接受两个参数: mapStateToProps
和 mapDispatchToProps
。
mapStateToProps
: 将redux
中的state
映射到 UI 组件的props
,UI组件内使用props
获取值。【可省略】该方法会订阅
Store
,每当state
更新的时候,就会自动执行,从而触发 UI 组件的相关事件。 如果省略,则不会订阅state
,即Store
更新不会引起 UI 组件的更新mapStateToProps
该方法包含两个参数:state
ownProps
:容器组件的props对象
mapDispatchToProps
:将用户对 UI 组件的操作映射成Action
。UI组件通过该函数触发action
继而改变store
可以是一个函数,也可以是一个对象。- 为函数时
会得到
dispatch
和ownProps
(容器组件的props
对象)两个参数。 需返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出Action
const mapDispatchToProps = (dispatch, ownProps) => { return { onClick: () => { dispatch({ type: 'SET_VISIBILITY_FILTER', filter: ownProps.filter }); } }; }
- 为对象时
它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出
const mapDispatchToProps = { onClick: (filter) => { type: 'SET_VISIBILITY_FILTER', filter: filter }; }
- 为函数时
会得到
Provider 组件
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import reducers from './reducers'
import App from './components/App'
let store = createStore(reducers);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Provider
组件当作根组件包裹其它组件,这时 App
下的所有子组件就都可以拿到 state
QA
1、问题: 通过 reducer 更新 state,虽然在 reducer 中改变了 state 的值,但是页面没有重新渲染
const initStore = [0, 1];
export default function (state = initStore, action) {
switch (action.type) {
case 'DELETE':
state.splice(action.index, 1);
console.log('state:',state);// 输出state的值已经改变
return state;
}
}
原因:reducer 只是一个纯函数,接收旧的 state 和 action,并返回新的 state 永远不要在 reducer 里做这些操作:
修改传入参数; 执行有副作用的操作,如 API 请求和路由跳转; 调用非纯函数,如 Date.now() 或 Math.random()。
Store 会把两个参数传入 reducer: 当前的 state 和 action,所以不能直接修改state,redux会比较新旧state的值,直接修改state会导致store内部的也发生改变,那么新旧state也就没有发生变化。页面就不会重新渲染。
解决:
不要修改 state。 使用 Object.assign()
或 ES7
的特性 { ...state, ...newState }
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
// Object.assign() 方法
return Object.assign({}, state, {
visibilityFilter: action.filter
});
// es7 语法特性
state.splice(action.index,1);
return [...state];
default:
return state
}
}
2、问题: Actions must be plain objects. Use custom middleware for async actions
原因:
使用异步 actions
时,需要 redux-thunk
中间件支持
解决:
// 增加 redux-thubk 中间件
import { createStore as _createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from 'redux-promise';
// 合并后的reducer
import reducer from './modules/reducer';
export default function createStore (initialState = {}) {
const store = createStore(
reducer,
initialState,
// 组合多个 middleware 形成 middleware链,扩展 actions
applyMiddleware(thunkMiddleware, promiseMiddleware)
);
return store;
}
示例
项目内多个 store
/**
* 程序主入口 index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import RouterMap from './router/routerMap';
import configureStore from './redux/index';
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<RouterMap />
</Provider>,
document.getElementById('app')
);
/**
* index.jsx
*/
import { combineReducers, createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import promiseMiddleware from 'redux-promise';
// 各reducer
import user from '../reducers/user';
// 组合多个reducer为一个
const rootReducer = combineReducers({
user
});
export default function createStore (initialState = {}) {
const store = createStore(
rootReducer,
initialState,
// 组合多个 middleware 形成 middleware链,扩展 actions
applyMiddleware(thunkMiddleware, promiseMiddleware)
);
return store;
}
/**
* user.jsx
* @desc store、actions、reducer
*/
// ActionType
const SAVE_USER_TOKEN= 'SAVE_USER_TOKEN';
const CLEAR_USER_TOKEN = 'CLEAR_USER_TOKEN';
const SAVE_USER_INFO = 'SAVE_USER_INFO';
// Store
const initialState = {
token: '',
userInfo: {}
};
// Reducer
export default function user (state = initialState, action) {
switch (action.type) {
case SAVE_USER_TOKEN:
return { ...state, token: action.data }
break;
case CLEAR_USER_TOKEN:
return { ...state, token: '' }
break;
case SAVE_USER_INFO:
return { ...state, userInfo: action.data }
break;
default:
return state;
}
}
// Actions
// 存储token
export function ActionSaveToken (token) {
return { type: SAVE_USER_TOKEN, data: token }
}
// 清除token
export function ActionClearToken () {
return { type: CLEAR_USER_TOKEN }
}
// 获取用户信息
export function ActionGetUserInfo () {
return dispatch => {
fetch.get('/user/getUserDetail')
.then((res) => {
// 触发对应的 reducer
dispatch({ type: SAVE_USER_INFO, data: res.data });
}, () => {
dispatch({ type: SAVE_USER_INFO, data: {} });
});
}
}
/**
* SiginIn.jsx
*/
import { connect } from 'react-redux';
import { ActionSaveToken, ActionGetUserInfo } from '../../redux/reducers/user';
// 第一种写法 (装饰器,需要额外配置)
@connect(
state => {
return {
token: state.user.token
};
}, {
ActionSaveToken,
ActionGetUserInfo
}
)
class User extends React.Component {
// ...TODO
// functions
this.props.saveToken('token content');
}
// 第二种写法
const mapStateToProps = (state) => {
return {
token: state.user.token
}
}
const mapDispatchToProps = () => {
return {
saveToken: (token) => dispatch(ActionSaveToken(token)),
getUserInfo: () => dispatch(ActionGetUserInfo())
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(User);
// 第三种写法
export default connect(
(state) => ({
token: state.user.token
}),
(dispatch) => ({
saveToken: (token) => dispatch(ActionSaveToken(token)),
getUserInfo: () => dispatch(ActionGetUserInfo())
})
)(LoginIn);
使用 装饰器 decorator
@connect
- 首先执行
yarn add babel-plugin-transform-decorators-legacy -S
- 配置相关的文件
// 在 `.babelrc` 文件新增
"plugins": [
"transform-decorators-legacy"
]
// webpack.config.js 中的 loader 处理器增加配置
{
test: /\.jsx$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['env', 'react', 'es2015'],
plugins: ['transform-decorators-legacy']
}
}
}
- 代码中开始使用
// 基于第二个示例代码做如下更新
import { connect } from 'react-redux';
import { UpdateCOS } from '../redux/modules/global';
@connect(
state => {
return {
nowDate: state.global.nowDate,
cos: state.global.COS
};
}, {
UpdateCOS
}
)
export default class Records extends React.Component {
// ...TODO
}
备注 vscode 编辑器
中出现如下警告
对修饰器的实验支持是一项将在将来版本中更改的功能。设置+"experimentalDecorators"+选项以删除此警告。
在 文件\首选项\设置
中搜索 experimentalDecorators
,在用户设置中 添加/修改为
"compilerOptions": {
"experimentalDecorators": true
}
// or
"javascript.implicitProjectConfig.experimentalDecorators": true