[Redux] Important things in Redux
Root Smart component can be overloaded, divide 'smart' component wisely & using Provider.
Problem: Something the root component can be overloaded, means it handle too many application logics. For a larger application, it will be hard to maintain.
Solution: We can use Provider from 'react-redux' to inject props into child component.
Install:
npm i -s react-redux
Wrap your <App /> into provider and provide store.
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {Router, Route, browserHistory} from 'react-router';
import {App} from './App'; import {configStore} from './store';
const store = configStore(); ReactDOM.render(
<Provider store={store}>
<Router history={browserHistory}>
<Route path="/(:filter)" component={App}></Route>
</Router>
</Provider>,
document.getElementById('root')
);
So from now on, we can divide logic into different smart component, so the root component doesn't need to handle many logic.
root component:
import React, { Component } from 'react';
import { VisibleTodoList, AddTodo } from './containers';
import {Footer} from './components';
export class TodoApp extends Component {
render() {
return (
<section>
<AddTodo />
<VisibleTodoList />
<Footer />
</section>
);
}
}
As you can see, root component becomes a dump component actually, can change to functional componet syntax if needed. So next let's see how to inject props and dispatch to those smart component.
Inject props and dispatch to smart component by using connect & withRouter.
This is somehow similar to Angular dependecy injection.
If we don't need component lifecycle, we can do:
import React from 'react';
import { connect } from 'react-redux';
import {withRouter} from 'react-router';
import { TodoList } from '../components';const mapStateToProps = (state, {params}) => ({
todos: TodosSelector.getVisibleTodos(params.filter, state.todos)
}); const mapDispatchToProps = (dispatch) => ({
onTodoClick: (id) => dispatch(toggleTodoAction(id))
}); /*
* VisibleTodoList: Will be the container component to render TodoList presentational component.
*
* TodoList: The presentational component to render the todos
*
* mapStateToProps: using connect to pass redux state ('todos'), make it available to TodoList component
*
* mapDispatchToProps: using connect to pass callback to make it available to TodoList component
* */
export const VisibleTodoList = withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(TodoList));
withRouter allows to inject router params into props.
Now, TodoList dump component will get props and dispatch action in props, so we can use.
import React from 'react';
import {Todo} from './todo.component'; export const TodoList = ({todos, onTodoClick}) => {
const list = todos.map(
todo => (
<Todo
{...todo}
key={todo.id}
onClick={() => onTodoClick(todo.id)}>
</Todo>
)
);
return (
<ul>
{list}
</ul>
);
};
If you do need component lifecycle, you can inject props into itself.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { TodoList } from '../components';
import { fetchingTodosAction } from '../../actions'
import { getVisibleTodos } from '../../reducers';
export class VisibleTodoList extends Component {
componentDidMount() {
this.fetchTodos();
}
componentDidUpdate(prevProps) {
if (this.props.params.filter !== prevProps.params.filter) {
this.fetchTodos();
}
}
fetchTodos = () => {
const { params: { filter = 'all' }, fetchingTodos } = this.props;
fetchingTodos(filter);
};
render() {
return (
<section>
<TodoList
{...this.props}
/>
</section>
);
}
}
const mapStateToProps = (state, { params }) => ({
todos: getVisibleTodos(params.filter, state.todos)
});
VisibleTodoList = withRouter(connect(
mapStateToProps,
{
onTodoClick: toggleTodoAction
}
)(VisibleTodoList));
So in the VisibleTodoList component, we reassign injected version to itself. In the component, we add two lifecycle methods 'componentDidMount' & 'componentDidUpdate'. componentDidMount is used for fetching data when component loaded, and componentDidUpdate will be triggered when props updated.
Shorthand syntax for mapDispatchToProps.
Notice here we didn't write mapDispatchToProps function instead we just pass an object:
VisibleTodoList = withRouter(connect(
mapStateToProps,
{
onTodoClick: toggleTodoAction
}
)(VisibleTodoList));
This is a shorthand syntax.
Another example for shorthand syntax.
/*
* We can reassign AddTodo = connect()(AddTodo);
* It will pass the dispatch function to AddTodo's props.
*
* In AddTodo Component, we don't need state, just need dispatch function.
* SO we can write:
* AddTodo = connect(
* null,
* dispatch => ({dispatch})
* )(AddTodo)
*
* And by default, connect function will link dispatch function to props,
* so it means we even don't need to pass the dispatch function
* SO we can write:
* AddTodo = connect(null, null)(AddTodo);
*
* In short, we can write:
* AddTodo = connect()(AddTodo);
* */
Introduce to "Selector" to the Reducers to encapsulate state shape.
We still has something to be improved in mapStateToProps function. Because the current mapStateToProps function requires component to know the state shape.
const mapStateToProps = (state, { params }) => ({
todos: getVisibleTodos(params.filter, state.todos)
});
This is not good enough because if state shape change later, we have to come back to all the component that related to it and modify those component.
What we want is keep component unchanged no matter how state shape change in the future.
So what we can do is using "selector" to encapsulate state shape inside reudcer. Notice that "selector" is nothing related to new libaray or functions from redux libarary. It is just a concept which we can (should) adopt from best partice.
For exmaple:
import { combineReducers } from 'redux';
/*
* Redux does not enforce that you encapsulate the knowledge about the state
* shape in particular reducer files.
* However, it's a nice pattern,
* because it lets you change the state that is stored by reducers
* without having to change your components or your tests
* if you use selectors together with reducers in your tests.
* */
const createList = (filter) => {
const ids = (state = [], action) => {
switch( action.type ) {
case 'FETCHING_TODOS_SUCCESS':
if(action.payload.filter !== filter){
return state;
}
return action.payload.response.result;
case 'ADD_TODO_SUCCESS':
return filter !== 'completed' ?
[...state, action.response.result]:
state;
default:
return state;
}
};
const isFetching = (state = false, action) => {
if (action.payload && action.payload.filter !== filter) {
return state;
}
switch( action.type ) {
case 'FETCHING_TODOS_SUCCESS':
return false;
case 'FETCHING_TODOS_FAILD':
return false;
case 'FETCHING_TODOS':
return true;
default:
return state;
}
};
const errorMessage = (state = null, action) => {
switch(action.type) {
case 'FETCHING_TODOS_FAILD':
return action.payload.message;
case 'FETCHING_TODOS':
case 'FETCHING_TODOS_SUCCESS':
return null;
default:
return state;
}
};
return combineReducers({
ids,
isFetching,
errorMessage
})
};
/*
Using default export for Reducer
*/
export default createList;
/*
Using name export for selectors.
Selectors are recommended to keep in the same location as redux
*/
export const getIds = (state) => state.ids;
export const isFetchingTodos = (state) => state.isFetching;
export const getErrorMessage = (state) => state.errorMessage;
Here we have three reducers combine as one single reducer export to outside (mainly for create store).
/*
Using default export for Reducer
*/
export default createList;
And we also have three selectors corresponding to three single reducers. The selector's job is to take the whole state from outside and return the piece of state which necessary.
export const getIds = (state) => state.ids;
export const isFetchingTodos = (state) => state.isFetching;
export const getErrorMessage = (state) => state.errorMessage;
And if you divide you reducer into multi levels (whic is also good), the high order reducer will NOT interactive which your low level reducer directly. It should go thought selector.
For example:
high order reducer, it call low level reducers' selector function we just see and export its selectors to outside:
import { combineReducers } from 'redux';
import byId, * as fromById from './byId.reducer';
import createList, * as fromList from './createList.reducer';
const listByFilter = combineReducers({
open: createList('open'),
completed: createList('completed'),
all: createList('all')
});
const todosReducer = combineReducers({
byId,
listByFilter
});
export default todosReducer;
/*
* Selectors
* */
export const getVisibleTodos = (filter, state) => {
const ids = fromList.getIds(state.todos.listByFilter[filter]);
return ids.map(id => fromById.getTodo(state.todos.byId, id));
};
export const getErrorMessage = (filter, state) => {
return fromList.getErrorMessage(state.todos.listByFilter[filter]);
};
export const isFetchingTodos = (state, filter) => {
return fromList.isFetchingTodos(state.todos.listByFilter[filter]);
};
So inside component, we can call selector function:
import { getVisibleTodos, isFetchingTodos, getErrorMessage } from '../../reducers';
const mapStateToProps = (state, { params }) => ({
todos: getVisibleTodos(params.filter, state), // we just pass in the whole state, selector will take care
errorMessage: getErrorMessage(params.filter, state),
isFetching: isFetchingTodos(state, params.filter)
});
To summary:
Rule1: Default export is for reducer, named export for selectors.
Rule2: Each reducer is recommended to have a selector (not must, but a good approach)
Rule3: Component will interactive each selector only NOT reudce, and we just need to pass in the whole state, let selector to figure out the right piece of state for the reducer.
Normalize your API response and return data shape.
It is quite normal that we get Array of object from backend. Then in the front end we deal with array.
But there is another way to doing this, which is similar to "Id lookup", so we will get an array of ids. This array is just a lookup table.
Then we will have an big object using the structure {[id]: objItem}. So we can use the id in the lookup table to query the item in the big object.
So the way we conver an array of object to lookup tables and big object pair is using normalizrlibaray.
Define a schema.js:
import {schema} from 'normalizr';
export const todoSchema = new schema.Entity('todos');
export const arrayOfTodosSchema = new schema.Array(todoSchema);
We create a entity call 'todos'. Since we know the response data from API is array, so we can define array of todos by using:
export const arrayOfTodosSchema = new schema.Array(todoSchema);
// OR
export const arrayOfTodosSchema = [todoSchema];
So in action creator, we can do this normalizion:
import { normalize } from 'normalizr';
import * as schema from './schema';
export const fetchingTodoSuccessAction = (response, filter) => ({
type: 'FETCHING_TODOS_SUCCESS',
payload: {
response: normalize(response, schema.arrayOfTodosSchema),
filter
}
});
For example:
The data we get from server is:
[
{
"id": ,
"name": "redux",
"completed": false
},
{
"id": ,
"name": "react",
"completed": true
}
]
After normalized, it should be:
{
result: [, ],
entities: {
"": { "id": "", "name": "redux", "completed": false },
"": { "id": "", "name": "react", "completed": true }
}
}
Remove side effect from action creator.
Normally you can use 'redux-thunk' or 'redux-promise' to handle async opreations in action creator. But it becomes hard to test for action creator. What is good is that keep action creator as a pure funciton. Handle effect side in a spreated thread.
There are many libaraies to do so, for exmaple 'redux-saga' & 'redux-observable'. Since I am quite used to RxJS. I choose 'redux-observble' as an example.
First, let's what the articuture can be when using side effect.

So as you can see, after dispatch an action, there are tow branches to handle the rest opreations. And those opreations are divided into tow parts.
1. All the sync oprations are handled by the Reducer.
2. All the async opreations are handled by Effect.
"Effect" is serving as a listener. Once match an async action, Effect will deal with "Service" which talk to server. After get the response from backend, we will normalizr it first and dispatch a new action. This action can be sync or async, if it is sync action, then after it is dispatched, the reducer will handle it.
For example,
You dispatch an action called 'FECHTING_TODOS'.
It will be only handled by Effect. After get response from server, will either dispatch 'FETCHING_TODOS_SUCCESS' or 'FETCHING_TODOS_ERROR'. And those actions will be handled by Reducer.
Effect:
import * as API from '../api';
import {
fetchingTodoSuccessAction,
fetchingTodoFaildAction
} from '../actions';
import { Observable } from 'rxjs'; export const fetchingTodoEpic = action$ =>
action$.ofType('FETCHING_TODOS')
.switchMap((action) => {
return API.getTodosAPI(action.payload.filter)
.map((response) =>
fetchingTodoSuccessAction(response, action.payload.filter))
.takeUntil(action$.ofType('CANCEL_REQUEST'))
.catch((err) => Observable.of(fetchingTodoFaildAction(err, action.payload.filter)));
});
Service:
export const getTodosAPI = (filter) => {
return Observable.ajax(`${baseURL}/${filter}`)
.retryWhen((err) => err.delay()
.take()
.concat(Observable.throw({
xhr: {
response: {
message: 'CANNOT FETCH TODOS'
}
}
})))
.map(data => data.response);
};
The benifits we can get form RxJS is really powerful API we can use, for example, 'switchMap', 'retryWhen'.
Downsides of redux-observable:
1. It is quite hard to test, even in the document, there is no suggested way to do test.
2. Quite a long learning curve to get used to it.
Source code: Github
[Redux] Important things in Redux的更多相关文章
- redux源码解析-redux的架构
redux很小的一个框架,是从flux演变过来的,尽管只有775行,但是它的功能很重要.react要应用于生成环境必须要用flux或者redux,redux是flux的进化产物,优于flux. 而且r ...
- react第十六单元(redux的认识,redux相关api的掌握)
第十六单元(redux的认识,redux相关api的掌握) #课程目标 掌握组件化框架实现组件之间传参的几种方式,并了解两个没有任何关系组件之间通信的通点 了解为了解决上述通点诞生的flux架构 了解 ...
- [Redux + Webpack] Hot reloading Redux Reducers with Webpack
Webpack will hot reload the component, but the reducer we need hard refresh. To sovle the problem, g ...
- Redux学习笔记:Redux简易开发步骤
该文章不介绍Redux基础,也不解释各种乱乱的概念,网上一搜一大堆.只讲使用Redux开发一个功能的步骤,希望可以类我的小白们,拜托它众多概念的毒害,大牛请绕道! 本文实例源代码参考:React-Re ...
- redux sample with slim redux source code
code sample没有package.json文件,也就没有任何外部依赖,直接使用slim redux source code. slim resux只有90多行. nodejs对es6的impo ...
- 1.Redux学习1,Redux
Redux流程图如上: Action就是一条命令 Store顾名思义就是存储数据的, Reducers是一个回调函数用于处理数据,它处理完数据会返回给Store存储起来 基本流程就是:组件中用Stor ...
- 4 react 简书 引入 redux 的 combineReducers 对 redux 数据进行管理
1. src 下的 common 下的 header 创建 store 文件夹 下创建 reducer.js # src/common/header/store/reducer.js const st ...
- React之redux学习日志(redux/react-redux/redux-saga)
redux官方中文文档:https://www.redux.org.cn/docs/introduction/CoreConcepts.html react-redux Dome:https://co ...
- redux学习总结
redux学习总结 *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !imp ...
随机推荐
- 《从零開始学Swift》学习笔记(Day 59)——代码排版
原创文章,欢迎转载.转载请注明:关东升的博客 代码排版包括: 空行.空格.断行和缩进等内容.代码排版内容比較多工作量非常多.可是非常重要. 空行 空行将逻辑相关的代码段分隔开.以提高可读性. 下列情况 ...
- HTML基础第七讲---框架
转自:https://i.cnblogs.com/posts?categoryid=1121494 框架(Frame)也就是所谓的分割窗口.分割画面.框窗效果(还真是五花八门),这个技巧在运用上问题比 ...
- 84.setlocale
用法示例 #include <Windows.h> #include <stdio.h> #include<locale.h> void main() { //se ...
- 4. Vue-Resource / axios 异步插件
安装 cnmp i vue-resource --save (--save 安装到dependencies下) 引用 <script src="node_modules/vue-res ...
- Excel数据比对-批量数据比对
1.导出现场的Excel收费规则2.有专门的代码写的测试收费规则的工具(开发自己开发的)3.在这个工具上选择,导出的收费规则Excel,点击导出按钮(导出按钮里面有计算每一列的计费结果4.Excel里 ...
- Android 迭代器 Iteraor迭代器以及foreach的使用
Iterator是一个迭代器接口,专门用来迭代各种Collection集合,包括Set集合和List集合. Java要求各种集合都提供一个iteratot()方法,该方法返回一个Iterator用于遍 ...
- C#复习题
1.以下(D )不是 C#中方法的參数的类型. A.值类型B.引用型C.输出型D.属性 2.C#中的数据类型分为值类型和引用类型,以下(B )不属于引用类型. A.类 B.枚举 C.接口 D.数组 3 ...
- ITFriend网站内测公测感悟
4月份做出网站Demo,就开始让用户使用了. 最初的黄色版界面,被吐槽得比较厉害. 关于界面,每个人都有自己的看法,只是喜欢和不喜欢的人比例不一样. 后来,花3400元请了个设计师,设计了一套界面,整 ...
- HIVE快速入门 分类: B4_HIVE 2015-06-06 11:27 59人阅读 评论(0) 收藏
(一)简单入门 1.创建一个表 create table if not exists ljh_emp( name string, salary float, gender string) commen ...
- 解决Linux动态库版本兼容问题
说道“动态库版本兼容”,很多人头脑中首先蹦出的就是“Dll Hell”.啊,这曾经让人头疼的难题.时至今日,这个难题已经很好地解决了. 在进一步讨论之前来思考一个问题:Linux下为什么没有让人头痛的 ...