React: 研究Redux的使用
一、简介
在上一篇文章中,大概讲了下Flux设计模式的使用,在末尾顺便提了一些基于Flux的脚本库,其中Redux已经毋庸置疑地成为了众多脚本库的翘楚之一。是的,Redux是基于Flux开发的,Redux库的尺寸非常小,代码量少,核心目的仍是处理数据流变化的问题。Redux并不完全是Flux,它只是类Flux的脚本库,它移除了Dispatcher这个概念,现在的结构是包含Store、Action、Action生成器、以及用于修改State的Action对象。在Redux中,对于State的管理采用了单个不可变对象来表示,同时还引入了Reducer纯函数来根据当前的State和Action返回一个新的State:(state, action) => newState。现在来对每一个成分结构进行分析。
二、State
在传统的React开发中,将State分布到每一个组件中基本上是通用的做法,这么做的好处是明显的,每个State数据由自己的组件去管理,需要那个组件做修改就让对应的组件去处理,但是,随着数据的增加,弊端也是明显的,管理应用程序的整体State可能就变得异常困难,因为要考虑到每个组件都将使用其内部的setState方法来改变自身的State,可能了解更新命令源也会变得更加复杂。然而,在Redux中则推荐将State尽量存放到少数几个对象中,这中处理方式几乎成了一条规则。现在来对比这两种不同的State管理分布,如下所示。可以发现Redux将所有的State数据都存储到了单个对象中,简化了在应用程序中查看和修改State的方式。
通过Redux,使得State管理与React完全剥离了,Redux将会直接管理这些State。从图中可以看出,Redux Store中所有的数据都植根于一个对象:State树。单个对象中的每一个键表示State树的一个分支。用图可以形象地表示如下:
用代码表示如下:
//state_data.js //存储的数据
//注意事项:后面本示例不处理kind对象,将subs补全为subjects
const state_data = {
names:[
{
"name_id":"",
"name":"张三",
},
{
"name_id":"",
"name":"李四",
}
],
subjects:[
{
"subject_id":"",
"subject":"数学",
},
{
"subject_id":"",
"subject":"语文",
}
],
scores:[
{
"score_id":"",
"score": ,
},
{
"score_id":"",
"score": ,
}
]
};
三、Action
根据Redux的规则,在上面已经确定了应用程序State的基本结构,数据都存放到了一个不可变的对象data中。不可变意味着data对象不可修改,此时只能通过整体替换的方式来更新data对象进而达到更新State的目的。在前面Flux中说过,Action是更新应用程序State的唯一方式,在Redux也是如此。对于State的每一种更新行为Action都是抽象的,开发者可以确认完Action后将其动词化。它的类型是字符串,通常由大写字母表示,并用下划线代替空格,做到见名知意为好。如下所示:
//action_constants.js //将Action行为动词化
const action_constants = {
ADD_NAME:"ADD_NAME", //添加姓名
DELETE_NAME:"DELETE_NAME", //删除姓名
UPDATE_NAME:"UPDATE_NAME", //更新姓名
ADD_SUBJECT:"ADD_SUBJECT", //添加科目
DELETE_SUBJECT:"DELETE_SUBJECT", //删除科目
UPDATE_SUBJECT:"UPDATE_SUBJECT", //更新科目
ADD_SCORE:"ADD_SCORE", //添加分数
DELETE_SCORE:"DELETE_SCORE", //删除分数
UPDATE_SCORE:"UPDATE_SCORE" //更新分数
}; export default action_constants;
一个Action就是一个JavaScript对象,它至少包含一个类型字段,如下所示:
//一个Action对象
{ type:"ADD_NAME" }
有的时候,开发者会把Action的type拼写错误,毕竟是字符串。为了解决这个问题,JavaScript的常量就派上了用场。将Action定义为常量可以使得开发者充分利用IDE智能提示和代码补全功能。这个一个预防失误的不错选择。如下所示:
//使用常量后,IDE都会有提示,开发非常方便
import C from './action_constants' {
type: C.ADD_NAME
}
{
type: C.UPDATE_NAME
}
Action是以JavaScript语法的形式提供更新某个State所需的一系列指令的,例如某些数据需要更新,某些数据需要删除,某些数据需要添加等等,这些数据可能是一个对象,也可能是一个字段,这类的数据统称为Action的有效载荷数据。Action是小而美的数据包,能够方便地告知Redux如何修改State。举例如下所示,可以发现第一个删除行为的Action的有效载荷数据不包括name字段。
// 删除姓名的Action对象
// type是必须要的,以便确认什么行为
// name_id也是必须要的,以便确认删除目标
{
type: C.UPDATE_DELETE,
name_id:""
} // 添加姓名的Action对象
// type是必须要的,以便确认什么行为
// name_id也是必须要的,新的标识
// name也是必须要的,新的姓名
{
type: C.ADD_NAME,
name_id:"",
name:"王五"
}
在Redux中,当通过store.dispatch()函数给每种Action派发执行时,都需要开发者去定义一个Action对象,这个过程虽说不难但是也是复杂的。开发者完全可以通过给每种Action类型添加一个Action生成器来简化Action的逻辑。如下所示:
//action_builder.js import C from './action_constants' //生成一个”添加姓名“的action
export const addName = (new_name_id, new_name) =>
({
type:C.ADD_NAME,
name_id:new_name_id
name:new_name
}); //生成一个”删除姓名“的action
export const deleteName = (name_id) =>
({
type:C.DELETE_NAME,
name_id
}); //生成一个”更新姓名“的action
export const updateName = (name_id, name) =>
({
type:C.UPDATE_NAME,
name_id,
name
});
四、Reducer
虽然现在整个State树都是存储到了单个对象中,但是它的模块化程度还是不高,例如开发者想用模块化的方式描述对象。Redux就是通过函数来实现模块化的,函数被用来更新部分State树中的内容,这些函数被称为Reducer。它会把当前的State和Action当做参数,然后返回一个新的State对象。Reducer的主要目的就是实现State树中部分数据的更新,包括叶子节点和分支。每一个Reducer都是模块化的体现,然后可以将多个Reducer合成一个Reducer,来处理应用程序中任意给定的Action的所有State更新。如图所示:names数组的Reducer处理的Action包括ADDNAME、DELETENAME、UPDATENAME。name对象的Reducer处理的Action包括ADDNAME和UPDATENAME。可以看出,接收的参数为数组,返回的也是数组,接收的参数是对象返回的也是一个对象。
每一个Reducer都可以被合成或者组合成单个的Reducer函数,以便在Store中使用。例如names数组的Reducer是由name对象的Reducer合成的。正是这种合成和单个函数的特点,提供了一种可以更新整个State树和处理任何接收的Action的方式。注意,Reducer合成不是必需的,这是一种推荐做法,我们也可以创建一个Reducer函数来处理所有的Action,但是这样就会失去模块化和函数式编程带来的优势。 用代码表示如下:
//action_reducer.js import C from './action_constants' //处理name对象的action的Reducer函数
export const name = (state={}, action) => { switch (action.type) {
case C.ADD_NAME:
return {
name_id:action.name_id,
name:action.name
};
case C.UPDATE_NAME:
return (state.name_id !== action.name_id) ? state : {
...state,
name:action.name
};
default:
return state;
}
}; //处理names数组的action的Reducer函数
export const names = (state=[], action) => { switch (action.type) {
case C.ADD_NAME:
return [
...state,
name({}, action)
];
case C.DELETE_NAME:
return state.filter(
name => name.name_id !== action.name_id
);
case C.UPDATE_NAME:
return state.map(
name => name(name, action)
);
default:
return state;
}
};
五、Store
同Flux的工作原理一样,Store负责完成应用程序中State数据的保存和处理所有State更新的地方。不同的是,Flux设计模式中可以支持多个Store共存,每一个Store只专注于特定的数据集。而Redux中只有一个Store,它会把当前的State和Action传递给每一个单独的Reducer函数进而来更新State。在使用Redux之前,需要在项目中安装和配置Redux,步骤如下:
1、npm安装redux相关组件【开发者根据需要安装需要的组件】
// redux: 本地数据存储空间,相当于本地数据库
// react-redux: 帮助完成数据订阅
// redux-thunk:帮助实现异步action
npm install --save redux react-redux redux-thunk // redux-logger: redux的日志中间件
npm install --save-dev redux-logger
2、yarn配置package.json包
Redux提供了关于Store的相关API,例如可以通过Redux的createStore函数通过传入参数State或Reducer创建Store,Redux还有一个专门的combineReducers函数来将所有的Reducer构造成单个Reducer。如我们所知,修改用户应用程序State的唯一方式就是通过Store分发Action。Store提供了一个getState函数获取保存到State数据。Store包含一个dispatch函数,可以接收Action作为参数,当用户通过Store分发某个Action时,该Action将会被分配到与之相应的Reducer上,然后State就被更新了。Store还允许使用subscribe函数订阅每次分发完一个Action后被触发的句柄函数,当然这个订阅函数会返回一个退订函数,可以用来退订之前订阅的监听器。相关API如下所示:
//创建Store
export interface StoreCreator {
<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S & StateExt, A> & Ext
<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
preloadedState?: DeepPartial<S>,
enhancer?: StoreEnhancer<Ext>
): Store<S & StateExt, A> & Ext
}
export const createStore: StoreCreator //combineReducers合成函数
export function combineReducers<S>(
reducers: ReducersMapObject<S, any>
): Reducer<S>
export function combineReducers<S, A extends Action = AnyAction>(
reducers: ReducersMapObject<S, A>
): Reducer<S, A> //获取State数据
getState(): S //集成自flux中的Dispatch,拥有Dispatch的所有公共函数
dispatch: Dispatch<A> //订阅函数,返回值是退订函数
subscribe(listener: () => void): Unsubscribe
顺便提一下,Redux不仅提供了combineReducers函数来合成Reducer,还提供了compose函数。 使用compose函数也可以将若干函数合成单个函数。它的功能很健壮,合成的函数执行的顺序是从右往左执行的。举例如下:
import { compose } from 'redux'
//需求:将scores数组的分数转成列表然后使用逗号拼接成字符串并打印 //1、一般写法
const scoreListString = store.getState().scores.map(s => s.score).join(",");
console.log(scoreListString); //2、使用compose合成单函数
//首先,从State中获取scores
//然后,绑定scores的映射函数map
//接着,生成一个score数组scoreList
//然后,使用逗号拼接字符串scoreListString
//最后,在控制台上打印结果
const print = compose(
scoreListString => console.log(scoreListString),
scoreList => scoreList.join(","),
map => map(s => s.score),
scores => scores.map.bind(scores),
state => state.scores
);
print(store.getState());
六、中间件
Redux还提供了中间件这个概念,它负责Store的分发管道功能。在Redux中,中间件是由在分发某个Action过程中一系列顺序执行的若干高阶函数构成的。这个高阶函数允许用户在某个Action被分发之前或之后,以及State被更新后,插入某些功能。每个中间件函数都是顺序执行的,它们拥有访问Action、dispatch函数,以及下一个next函数的权限。next函数将会触发更新操作。在next函数被调用之前,State就会被更新。网络上介绍的流程图大致如下:
现在,我们就在Store中使用中间件 。首先创建一个storeFactory工厂类,这个类专门用来管理Store的创建过程。然后,通过这个工厂类接着创建一个包含了日志记录的中间件的Store。storeFactory工厂类所在文件包含一个函数,并导出它,该函数用来对创建Store所需的数据进行分组。当需要用到Store时,直接通过这个函数进行创建即可:
//使用工厂类创建store
const store = storeFactory(initData);
在storeFactory类中插入中间件,需要用到redux中的applyMiddleware应用中间件函数。定义和插入logger中间件,如下所示:
//store_factory.js import { createStore, combineReducers, applyMiddleware } from 'redux'
import { name, names, subject, subjects, score, scores} from "./action_reducer";
import state_data from "./state_data"; //创建日志中间件
const logger = store => next => action => {
let result;
console.groupCollapsed("dispatching", action.type);
console.log('prev state', store.getState());
console.log('action', action);
result = next(action);
console.log('next state', store.getState());
console.groupEnd();
}; //创建store工厂函数
//0. 设置应用中间件applyMiddleware函数
//1. 插入中间件logger
//2. 合成Reducer函数
const storeFactory = (stateData=state_data) =>
applyMiddleware(logger)(createStore)(
combineReducers({name,subject,score,names,subjects,scores}),
state_data
) export default storeFactory
分析结果:在logger中,在该Action被分发之前,打开了一个新的控制台分组console.groupCollapsed,记录了当前的State和Action。触发了next管道上的Action顺序执行中间件函数,最终到达Reducer。此时的State已经更新了,因此我们记录被修改过的State,最后在控制台上分组显示。console.groupEnd()代表中分组结束。
七、综合应用
下面是完整的代码文件,如下所示:
1、State数据文件:state_data.js
//存储的数据
const state_data = {
names:[
{
"name_id":"",
"name":"张三",
},
{
"name_id":"",
"name":"李四",
}
],
subjects:[
{
"subject_id":"",
"subject":"数学",
},
{
"subject_id":"",
"subject":"语文",
}
],
scores:[
{
"score_id":"",
"score": ,
},
{
"score_id":"",
"score": ,
}
]
}; export default state_data;
2、Action动词文件:action_constants.js
//定义action类型的动词
const action_constants = {
ADD_NAME:"ADD_NAME", //添加姓名
DELETE_NAME:"DELETE_NAME", //删除姓名
UPDATE_NAME:"UPDATE_NAME", //更新姓名
ADD_SUBJECT:"ADD_SUBJECT", //添加科目
DELETE_SUBJECT:"DELETE_SUBJECT", //删除科目
UPDATE_SUBJECT:"UPDATE_SUBJECT", //更新科目
ADD_SCORE:"ADD_SCORE", //添加分数
DELETE_SCORE:"DELETE_SCORE", //删除分数
UPDATE_SCORE:"UPDATE_SCORE" //更新分数
}; export default action_constants;
3、Action生成器文件:action_builder.js
import C from './action_constants' //生成一个"添加姓名"的action
export const addName = (new_name_id, new_name) =>
({
type:C.ADD_NAME,
name_id:new_name_id,
name:new_name
}); //生成一个"删除姓名"的action
export const deleteName = (name_id) =>
({
type:C.DELETE_NAME,
name_id
}); //生成一个"更新姓名"的action
export const updateName = (name_id, name) =>
({
type:C.UPDATE_NAME,
name_id,
name
}); //生成一个"添加科目"的action
export const addSubject = (new_subject_id, new_subject) =>
({
type:C.ADD_SUBJECT,
subject_id:new_subject_id,
subject:new_subject
}); //生成一个"删除科目"的action
export const deleteSubject = (subject_id) =>
({
type:C.DELETE_SUBJECT,
subject_id
}); //生成一个"更新科目"的action
export const updateSubject = (subject_id, subject) =>
({
type:C.UPDATE_SUBJECT,
subject_id,
subject
}); //生成一个"添加分数"的action
export const addScore = (new_score_id, new_score) =>
({
type:C.ADD_SCORE,
score_id:new_score_id,
score:new_score
}); //生成一个"删除分数"的action
export const deleteScore = (score_id) =>
({
type:C.DELETE_SCORE,
score_id
}); //生成一个"更新分数"的action
export const updateScore = (score_id, score) =>
({
type:C.UPDATE_SCORE,
score_id,
score
});
4、Reducer函数文件:action_reducer.js
import C from './action_constants' //处理name对象的action的Reducer函数
export const name = (state={}, action) => { switch (action.type) {
case C.ADD_NAME:
return {
name_id:action.name_id,
name:action.name
};
case C.UPDATE_NAME:
return (state.name_id !== action.name_id) ? state : {
...state,
name:action.name
};
default:
return state;
}
}; //处理names数组的action的Reducer函数
export const names = (state=[], action) => { switch (action.type) {
case C.ADD_NAME:
return [
...state,
name({}, action)
];
case C.DELETE_NAME:
return state.filter(
name => name.name_id !== action.name_id
);
case C.UPDATE_NAME:
return state.map(
nameObj => name(nameObj, action)
);
default:
return state;
}
}; //处理subject对象的action的Reducer函数
export const subject = (state={}, action) => { switch (action.type) {
case C.ADD_SUBJECT:
return {
subject_id:action.subject_id,
subject:action.subject
};
case C.UPDATE_SUBJECT:
return (state.subject_id !== action.subject_id) ? state : {
...state,
subject:action.subject
};
default:
return state;
}
}; //处理subjects数组的action的Reducer函数
export const subjects = (state=[], action) => { switch (action.type) {
case C.ADD_SUBJECT:
return [
...state,
subject({}, action)
];
case C.DELETE_SUBJECT:
return state.filter(
subject => subject.subject_id !== action.subject_id
);
case C.UPDATE_SUBJECT:
return state.map(
subjectObj => subject(subjectObj, action)
);
default:
return state;
}
}; //处理score对象的action的Reducer函数
export const score = (state={}, action) => { switch (action.type) {
case C.ADD_SCORE:
return {
score_id:action.score_id,
score:action.score
};
case C.UPDATE_SCORE:
return (state.score_id !== action.score_id) ? state : {
...state,
score:action.score
};
default:
return state;
}
}; //处理scores数组的action的Reducer函数
export const scores = (state=[], action) => { switch (action.type) {
case C.ADD_SCORE:
return [
...state,
score({}, action)
];
case C.DELETE_SCORE:
return state.filter(
score => score.score_id !== action.score_id
);
case C.UPDATE_SCORE:
return state.map(
scoreObj => score(scoreObj, action)
);
default:
return state;
}
};
5、Store工厂类文件:store_factory.js
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { name, names, subject, subjects, score, scores} from "./action_reducer";
import state_data from "./state_data"; //创建日志中间件
const logger = store => next => action => {
console.groupCollapsed("dispatching", action.type);
console.log('prev state:', store.getState());
console.log('action:', action);
next(action);
console.log('next state:', store.getState());
console.groupEnd();
}; //创建store工厂函数
//0. 设置应用中间件applyMiddleware函数
//1. 插入中间件logger
//2. 合成Reducer函数
const storeFactory = (stateData=state_data) =>
applyMiddleware(logger)(createStore)(
combineReducers({name, names, subject, subjects, score, scores}),
state_data
) export default storeFactory;
下面是测试代码,如下所示:
1、获取state数据
//获取state数据
import state_data from "./redux/state_data";
console.log("1————state_data:",state_data);
2、手动创建action,action中至少有一个type字段用于区分类别
//手动创建action,action中至少有一个type字段用于区分类别
import C from './redux/action_constants'
const manually_addName_Action = { type: C.ADD_NAME, name_id:"", name:"王五"};
const manually_deleteName_Action = { type: C.DELETE_NAME, name_id:""};
const manually_updateName_Action = { type: C.UPDATE_NAME, name_id:"", name:"赵六"};
console.log("manually_addName_Action:", manually_addName_Action);
console.log("manually_deleteName_Action:", manually_deleteName_Action);
console.log("manually_updateName_Action:", manually_updateName_Action); const manually_addSubject_Action = { type: C.ADD_SUBJECT, subject_id:"", subject:"地理"};
const manually_deleteSubject_Action = { type: C.DELETE_SUBJECT, subject_id:""};
const manually_updateSubject_Action = { type: C.UPDATE_SUBJECT, subject_id:"", subject:"政治"};
console.log("manually_addSubject_Action:", manually_addSubject_Action);
console.log("manually_deleteSubject_Action:", manually_deleteSubject_Action);
console.log("manually_updateSubject_Action:", manually_updateSubject_Action); const manually_addScore_Action = { type: C.ADD_SCORE, score_id:"", score:};
const manually_deleteScore_Action = { type: C.DELETE_SCORE, score_id:""};
const manually_updateScore_Action = { type: C.UPDATE_SCORE, score_id:"", score:};
console.log("manually_addScore_Action:", manually_addScore_Action);
console.log("manually_deleteScore_Action:", manually_deleteScore_Action);
console.log("manually_updateScore_Action:", manually_updateScore_Action);
3、使用生成器创建action
//使用生成器创建action
import {addName, deleteName, updateName,
addSubject, deleteSubject, updateSubject,
addScore, deleteScore, updateScore} from "./redux/action_builder"; const auto_addName_Action = addName("","王五");
const auto_deleteName_Action = deleteName("");
const auto_updateName_Action = updateName("","赵六");
console.log("auto_addName_Action:", auto_addName_Action);
console.log("auto_deleteName_Action:", auto_deleteName_Action);
console.log("auto_updateName_Action:", auto_updateName_Action); const auto_addSubject_Action = addSubject("","地理");
const auto_deleteSubject_Action = deleteSubject("");
const auto_updateSubject_Action = updateSubject("","政治");
console.log("auto_addSubject_Action:", auto_addSubject_Action);
console.log("auto_deleteSubject_Action:", auto_deleteSubject_Action);
console.log("auto_updateSubject_Action:", auto_updateSubject_Action); const auto_addScore_Action = addScore("", );
const auto_deleteScore_Action = deleteScore("");
const auto_updateScore_Action = updateScore("", );
console.log("auto_addScore_Action:", auto_addScore_Action);
console.log("auto_deleteScore_Action:", auto_deleteScore_Action);
console.log("auto_updateScore_Action:", auto_updateScore_Action);
4、Reducer函数,模块化
//打印对象的Reducer和数组的Reducer
import { name, subject, score, names, subjects, scores } from "./redux/action_reducer"; console.log("name_reducer:",name);
console.log("subject_reducer:",subject);
console.log("score_reducer:",score);
console.log("names_reducer:",names);
console.log("subjects_reducer:",subjects);
console.log("scores_reducer:",scores);
5、合成多个Reducer成单个Reducer
// 合成多个Reducer为单个Reducer
import {combineReducers} from "redux"; const combineReducer = combineReducers(
{ name, subject, score, names, subjects, scores});
console.log("combineReducer:",combineReducer);
6、定义store,分派action,观察state数据的变化
// 定义store,分派action
import {createStore} from "redux"; const store = createStore(combineReducer, state_data);
console.log("store:",store); store.dispatch(auto_addName_Action);
console.log("2————state_data:",store.getState());
7、订阅Store,添加logger句柄函数,打印日志
//订阅store,添加一个打印subjects的句柄函数
const logger = () => console.log('subscribe-----subjects:',store.getState().subjects);
const unsubscribeLogger = store.subscribe(logger); //分发store,触发句柄函数
store.dispatch(auto_addSubject_Action);
store.dispatch(auto_deleteSubject_Action);
store.dispatch(auto_updateSubject_Action); //退订监听器
unsubscribeLogger();
8、插入中间件,在控制台分组中观察state数据变化以及logger打印插件的结果
//通过类方法创建store
import storeFactory from "./redux/store_factory"; const store = storeFactory();
console.log("store:", store); //派发Action
store.dispatch(auto_addName_Action);
console.log("3————state_data:",store.getState()); store.dispatch(auto_deleteName_Action);
console.log("4————state_data:",store.getState()); store.dispatch(auto_updateName_Action);
console.log("5————state_data:",store.getState());
React: 研究Redux的使用的更多相关文章
- 实例讲解react+react-router+redux
前言 总括: 本文采用react+redux+react-router+less+es6+webpack,以实现一个简易备忘录(todolist)为例尽可能全面的讲述使用react全家桶实现一个完整应 ...
- 基于 React.js + Redux + Bootstrap 的 Ruby China 示例 (转)
一直学 REACT + METEOR 但路由部分有点问题,参考一下:基于 React.js + Redux + Bootstrap 的 Ruby China 示例 http://react-china ...
- 基于react+react-router+redux+socket.io+koa开发一个聊天室
最近练手开发了一个项目,是一个聊天室应用.项目虽不大,但是使用到了react, react-router, redux, socket.io,后端开发使用了koa,算是一个比较综合性的案例,很多概念和 ...
- 最新的chart 聊天功能( webpack2 + react + router + redux + scss + nodejs + express + mysql + es6/7)
请表明转载链接: 我是一个喜欢捣腾的人,没事总喜欢学点新东西,可能现在用不到,但是不保证下一刻用不到. 我一直从事的是依赖angular.js 的web开发,但是我怎么能一直用它呢?看看最近火的一塌糊 ...
- 【前端】react and redux教程学习实践,浅显易懂的实践学习方法。
前言 前几天,我在博文[前端]一步一步使用webpack+react+scss脚手架重构项目 中搭建了一个react开发环境.然而在实际的开发过程中,或者是在对源码的理解中,感受到react中用的最多 ...
- 【前端,干货】react and redux教程学习实践(二)。
前言 这篇博文接 [前端]react and redux教程学习实践,浅显易懂的实践学习方法. ,上一篇简略的做了一个redux的初级demo,今天深入的学习了一些新的.有用的,可以在生产项目中使用的 ...
- [React] 14 - Redux: Redux Saga
Ref: Build Real App with React #14: Redux Saga Ref: 聊一聊 redux 异步流之 redux-saga [入门] Ref: 从redux-thun ...
- [React] 15 - Redux: practice IM
本篇属于私人笔记. client 引导部分 一.assets: 音频,图片,字体 ├── assets │ ├── audios │ ├── fonts │ └── images 二.main&quo ...
- react脚手架改造(react/react-router/redux/eslint/karam/immutable/es6/webpack/Redux DevTools)
公司突然组织需要重新搭建一个基于node的论坛系统,前端采用react,上网找了一些脚手架,或多或少不能满足自己的需求,最终在基于YeoMan的react脚手架generator-react-webp ...
随机推荐
- 勾股数专题-SCAU-1079 三角形-18203 神奇的勾股数(原创)
勾股数专题-SCAU-1079 三角形-18203 神奇的勾股数(原创) 大部分的勾股数的题目很多人都是用for来便利,然后判断是不是平方数什么什么的,这样做的时候要对变量类型和很多细节都是要掌握好的 ...
- 打算写一个《重学Node.js》系列,希望大家多多支持
先放上链接吧,项目已经开始2周了:https://github.com/hellozhangran/happy-egg-server 想法 现在是2019年11月24日,还有人要开始学习Node.js ...
- python+selenium +unittest生成HTML测试报告
python+selenium+HTMLTestRunner+unittest生成HTML测试报告 首先要准备HTMLTestRunner文件,官网的HTMLTestRunner是python2语法写 ...
- Java基础IO类之对象流与序列化
对象流的两个类: ObjectOutputStream:将Java对象的基本数据类型和图形写入OutputStream ObjectInputStream:对以前使用ObjectOutputStrea ...
- C语言I博客作业10
这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 https://edu.cnblogs.com/campus/zswxy/CST2019-1/homework/10095 我在这个课程的 ...
- 网页采集器-UA伪装
网页采集器-UA伪装 UA伪装 请求载体身份标识的伪装: User-Agent: 请求载体身份标识,通过浏览器发起的请求,请求载体为浏览器,则该请求的User-Agent为浏览器的身份标识,如果使用爬 ...
- Tomcat连接器详解
1.连接器等同于nginx中的引擎. 2.tomcat连接器有三种运行模式bio.nio.apr . (1)bio(blocking I/O,阻塞式I/O操作) 1)表示tomcat使用的是传统的ja ...
- hello gulp,使用gulp的第一天。
昨天花了一天的时间,学习了一下gulp,今天整理一下,也分享给朋友们. 首先当然是去gulp的官网逛一圈了: http://gulpjs.com/ 中文站地址: http://www.gulpjs.c ...
- LNMP的搭建 及地址转换
1. LNMP 先安装nginx yum -y install gcc openssl-devel pcre-devel wget http://nginx.org/download/ngin ...
- mac install: /usr/bin/unrar: Operation not permitted
按照教程mac下解压缩rar文件工具-rarosx(免费),在mac上安装rar,在执行命令 sudo install -c -o $USER unrar /bin 出现错误:install: /bi ...