Action
Action 是把数据从应用 (服务器响应, 用户输入或其它非 view 的数据) 传到 store 的有效载荷. 它是 store 数据的唯一来源. 一般来说你会通过 store.dispatch() 将 action 传到 store. 分下边两类
- /*
- * action 常量
- */
- export const ADD_TODO = 'ADD_TODO';
- export const VisibilityFilters = {
- SHOW_ALL: 'SHOW_ALL',
- SHOW_COMPLETED: 'SHOW_COMPLETED',
- }
- /*
- * action 创建函数
- */
- export function addTodo(text) {
- return { type: ADD_TODO, text }
- }
- export const addTodo = (id)=>{
- return {
- type: EDITORUSERID,
- id:id
- }
- }
- reducer
是一个纯函数, 接收旧的 state 和 action, 返回新的 state.
(previousState, action) => newState
注意: 永远不要在 reducer 里做这些操作:
修改传入参数;
执行有副作用的操作, 如 API 请求和路由跳转;
调用非纯函数, 如 Date.now() 或 Math.random().
- function todos(state = [], action) {
- switch (action.type) {
- case ADD_TODO:
- return [
- ...state,
- {
- text: action.text,
- completed: false
- }
- ]
- default:
- return state
- }
- }
combineReducers 管理多个 Reducer
- const todoApp = combineReducers({
- visibilityFilter,
- todos
- })
- export default todoApp
当你触发 action 后, combineReducers 返回的 todoApp 会负责调用两个 reducer:
let nextTodos = todos(state.todos, action)
注意: 也可以 reducer 放到一个独立的文件中, 通过 export 暴露出每个 reducer 函数 import * as reducers from './reducers'
Store
Store 有以下职责:
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器
- import todoApp from './reducers'
- let store = createStore(todoApp)
createStore() 的第二个参数是可选的, 用于设置 state 初始状态. 这对开发同构应用时非常有用, 服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化.
let store = createStore(todoApp, window.STATE_FROM_SERVER)
容器组件
Redux 的 React 绑定库是基于 容器组件和 zhan shi 组件相分离 的开发思想
展示组件 | 容器组件 | |
---|---|---|
作用 | 描述如何展现(骨架、样式) | 描述如何运行(数据获取、状态更新) |
直接使用 Redux | 否 | 是 |
数据来源 | props | 监听 Redux state |
数据修改 | 从 props 调用回调函数 | 向 Redux 派发 actions |
调用方式 | 手动 | 通常由 React Redux 生成 |
展示组件就是一般的 js 文件容器组件往往使用
connect(mapStateToProps,mapDispatchToProps)
创建. mapStateToProps 是把容器组件 state 向展示组件 props 映射. mapDispatchToProps() 是映射回调方法. 例如, 我们希望 VisibleTodoList 向 TodoList 组件中注入一个叫 mOnClick 的 props , 还希望 onTodoClick 能分发 increaseAction 这个 action:
- const App=connect(
- (state)=>({
- value:state.count
- }),(dispatch)=>({
- mOnClick:()=>dispatch(increaseAction)
- })
- )(Counter);
- Provider
所有容器组件都可以访问 Redux store, 建议的方式是使用指定的 React Redux 组件 来包裹, 让所有容器组件都可以访问 store,
- let store = createStore(todoApp)
- render(
- <Provider store={store}>
- <App />
- </Provider>,
- document.getElementById('root')
- )
- // 创建组件的简单写法
- const App = () => (
- <div>
- <AddTodo />
- <VisibleTodoList />
- <Footer />
- </div>
- )
- export default App
高级部分(处理异步 Action)
标准的做法是使用 Redux Thunk 中间件. action 创建函数除了返回 action 对象外还可以返回函数. 这时, 这个 action 创建函数就成为了 thunk. 这个函数会被 Redux Thunk middleware 执行.
我们仍可以在 actions.js 里定义这些特殊的 thunk action 创建函数.
创建 thunk action
- //thunk action
- // 虽然内部操作不同, 你可以像其它 action 创建函数 一样使用它:
- // store.dispatch(fetchPosts('reactjs'))
- export function fetchPosts(subreddit) {
- return function (dispatch) {
- // 首次 dispatch: 更新应用的 state 来通知
- // API 请求发起了.
- dispatch(requestPosts(subreddit))
- return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
- .then(
- response => response.json(),
- error => console.log('An error occurred.', error)
- )
- .then(json =>
- dispatch(receivePosts(subreddit, json))
- )
- }
- }
- export function fetchPostsIfNeeded(subreddit) {
- // 当缓存的值是可用时,
- // 减少网络请求很有用.
- return (dispatch, getState) => {
- if (shouldFetchPosts(getState(), subreddit)) {
- // 在 thunk 里 dispatch 另一个 thunk!
- return dispatch(fetchPosts(subreddit))
- } else {
- // 告诉调用代码不需要再等待.
- return Promise.resolve()
- }
- }
- }
- middleware
你可以利用 Redux middleware 来进行日志记录, 创建崩溃报告, 调用异步接口或者路由等等. 应用中间件要改造下 createStore()
* 记录所有被发起的 action 以及产生的新的 state.
- */
- const logger = store => next => action => {
- console.group(action.type)
- let result = next(action)
- console.log('next state', store.getState())
- return result
- }
- let store = createStore(
- todoApp,
- applyMiddleware(
- logger
- )
优化减少模版代码
1 action 优化 1.1 你可以写一个用于生成 action creator 的函数:
- function makeActionCreator(type, ...argNames) {
- return function(...args) {
- let action = { type }
- argNames.forEach((arg, index) => {
- action[argNames[index]] = args[index]
- })
- return action
- }
- }
- const ADD_TODO = 'ADD_TODO'
- const EDIT_TODO = 'EDIT_TODO'
- export const addTodo = makeActionCreator(ADD_TODO, 'todo')
- export const editTodo = makeActionCreator(EDIT_TODO, 'id', 'todo')
1.2 异步 Action Creators
- export function loadPosts(userId) {
- return {
- // 要在之前和之后发送的 action types
- types: ['LOAD_POSTS_REQUEST', 'LOAD_POSTS_SUCCESS', 'LOAD_POSTS_FAILURE'],
- // 检查缓存 (可选):
- shouldCallAPI: (state) => !state.users[userId],
- // 进行取:
- callAPI: () => fetch(`http://myapi.com/users/${userId}/posts`),
- // 在 actions 的开始和结束注入的参数
- payload: { userId }
- };
- }
2.reducer 重构
方法抽取
- function addTodo(state, action) {
- ...
- return updateObject(state, {todos : newTodos});
- }
- function todoReducer(state = initialState, action) {
- switch(action.type) {
- case 'SET_VISIBILITY_FILTER' : return setVisibilityFilter(state, action);
- case 'ADD_TODO' : return addTodo(state, action);
- default : return state;
- }
- }
善用 combineReducers 函数
- // 使用 ES6 的对象字面量简写方式定义对象结构
- const rootReducer = combineReducers({
- todoReducer,
- firstNamedReducer
- });
- const store = createStore(rootReducer);
3. 大多数应用会处理多种数据类型, 通常可以分为以下三类:
域数据(Domain data): 应用需要展示, 使用或者修改的数据(比如 从服务器检索到的所有 todos
应用状态(App state): 特定于应用某个行为的数据(比如 "Todo #5 是现在选择的状态", 或者 "正在进行一个获取 Todos 的请求")
UI 状态(UI state): 控制 UI 如何展示的数据(比如 "编写 TODO 模型的弹窗现在是展开的")
一个典型的应用 state 大致会长这样:
- {
- domainData1 : {},
- domainData2 : {},
- appState1 : {},
- appState2 : {},
- ui : {
- uiState1 : {},
- uiState2 : {},
- }
- }
必要时可采用 Redux-ORM
更多示例 https://github.com/reactjs/redux 感谢 http://cn.redux.js.org/docs/recipes/reducers/UpdatingNormalizedData.html
来源: https://juejin.im/post/5acb67fef265da23a049de90