react+webpack+react-router+redux项目搭建(三)

(8) Hot Module Replacement

从上可以发现,每当在js内修改内容,都需要重新编译。因此我们使用webpack的一个热替换插件Hot Module Replacement,当在编辑页面修改代码时,浏览器会自动刷新。使用功能如下:

a. 配置

在package.json 增加 --hot

"dev": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"

或者

在webpack.dev.config.js中添加配置

+ const webpack = require('webpack');
+ devServer: { hot: true}
+ plugins:[ new webpack.HotModuleReplacementPlugin()]

我们分别尝试一下上面的配置,并使用下面的重置state案例:
修改Home.js,增加计数state


import React, {Component} from 'react';

export default class Home extends Component {
 constructor(props) {
 super(props);
 this.state = {
 count: 0
 }
 }
 _handleClick() {
 this.setState({
 count: ++this.state.count
 })
 }

 render() {
 return (
 <div>
 This is Home<br/>
  当前计数:{this.state.count}<br/>
 <button onClick={() => this._handleClick()}>自增</button>
 </div>
 ) }
}

在src/index.js 增加module.hot.accept()。当模块更新的时候,通知index.js。

import React from 'react';
import ReactDom from 'react-dom';
import getRouter from './router/router';

if (module.hot) {
 module.hot.accept();
}

ReactDom.render(
 getRouter(), document.getElementById('app')
);
(9) react-hot-loader

在第(8)节中,当文档内容发生变化时,所有的state会重新被初始化,如果想只刷新页面,但是state不改变这是就要使用react-hot-loader。其学习教程参见https://github.com/gaearon/react-hot-loader

a.安装

npm install --save react-hot-loader

b.配置

.babel文件,添加

{
 "plugins": ["react-hot-loader/babel"]
}

Webpack.dev.config.js文件,入口添加react-hot-loader/patch

entry: [
 'react-hot-loader/patch',
 path.join(__dirname, 'src/index.js')
 ]

修改src/index.js

import React from 'react';
import ReactDom from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import getRouter from './router/router';
/*初始化*/
// 如果没该步骤,页面会出现空白
renderWithHotReload(getRouter());
/*热更新*/
if (module.hot) {
 module.hot.accept('./router/router.js', () => {
const getRouter = require('./router/router.js').default;
 renderWithHotReload(getRouter());
 });
}

function renderWithHotReload(RootElement) {
 ReactDom.render(
 <AppContainer>
 {RootElement}
 </AppContainer>,
 document.getElementById('app')
 )
}

运行npm ryun start查看效果

(10)webpack配置文件路径优化

在引用Home.js时需要使用相对路径,import Home from '../pages/Home/Home';
然而,在(4).e中提到可以使用webpack配置中的alia,为’../pages’提供别名,如下所示:
webpack.dev.config.js

 resolve: {
 alias: {
 pages: path.join(__dirname, 'src/pages'),
 component: path.join(__dirname, 'src/component'),
 router: path.join(__dirname, 'src/router')
 }
 }

把之前的相对路径
import Home from '../pages/Home/Home';改为import Home from 'pages/Home/Home';(

(11)redux

通过redux对state管理实现自增、自减和重置。
想说一说redux中重要的三个概念,其余内容将在redux专题中讲解。

① action
Action 是把数据从应用(这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。表示发生了什么
Action 本质上是JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。如todo任务:

const ADD_TODO = 'ADD_TODO'
{
 type: ADD_TODO,
 text: 'Build my first Redux app'
}

除了type 字段外,action 对象的结构完全由你自己决定
Action 创建函数就是生成 action 的方法。
Action 创建函数也可以是异步非纯函数,我们将在redux专题中详细介绍。
② reducer
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。所以,reducer就是纯函数,接收state 和 action,然后返回一个新的 state。
后面会详细介绍多个reducer组合使用。
③ store
前面使用 action 来描述“发生了什么”,和使用 reducers 来根据 action 更新 state 的用法。
Store 就是把它们联系到一起的对象。Store 有以下职责:
**
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器;
通过 subscribe(listener) 返回的函数注销监听器
**
再次强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。
将多个reducer合并进行后,并导入到redux/store.js,并传递 createStore()。

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

createStore() 的第二个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。

let store = createStore(todoApp, window.STATE_FROM_SERVER)

在test.js中发起 Actions,

//Import action
//注册监听
const unsubscribe = store.subscribe();
//发起action
store.dispatch(…action);
//停止监听
Unsubscribe();

④数据流
Redux是严格的单向数据流,也就是说,所有的数据遵循相同的生命周期,这么做的优势是可以让应用的数据变化更加简单。当组件发生变化时,只需要关注dispatch它的store,并不需要关心其他组件中的state是否发生变化。
同时也鼓励做数据范式化(范式化?),这样可以避免使用多个且独立的无法相互引用的重复数据。什么意思

i.调用 store.dispatch(action)
Action 就是一个描述“发生了什么”的普通对象。比如:

 { type: 'LIKE_ARTICLE', articleId: 42 };
 { type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } };
 { type: 'ADD_TODO', text: 'Read the Redux docs.'};

可以把 action 理解成新闻的摘要。如“玛丽喜欢42号文章。”或者“任务列表里添加了'学习 Redux 文档'”。

你可以在任何地方调用 store.dispatch(action),包括组件中、XHR 回调中、甚至定时器中。
ii. Redux store 调用传入的 reducer 函数。
Store 会把两个参数传入 reducer: 当前的 state 树和 action。例如,在这个 todo 应用中,根 reducer 可能接收这样的数据:

// 当前应用的 state(todos 列表和选中的过滤器)
 let previousState = {
 visibleTodoFilter: 'SHOW_ALL',
 todos: [
 {
 text: 'Read the docs.',
 complete: false
 }
 ]
 }
// 将要执行的 action(添加一个 todo)
 let action = {
 type: 'ADD_TODO',
 text: 'Understand the flow.'
 }
// reducer 返回处理后的应用状态
 let nextState = todoApp(previousState, action);

iii. 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
根 reducer 的结构完全由你决定。Redux 原生提供combineReducers()辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。
当你触发 action 后,combineReducers 返回的 todoApp 会负责调用两个 reducer:

 let nextTodos = todos(state.todos, action);
 let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action);

然后会把两个结果集合并成一个 state 树:

 return {
 todos: nextTodos,
 visibleTodoFilter: nextVisibleTodoFilter
 };

iv. Redux store 保存了根 reducer 返回的完整 state 树。
这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。
现在,可以应用新的 state 来更新 UI。如果你使用了 React Redux 这类的绑定库,这时就应该调用 component.setState(newState) 来更新。

a.安装redux

npm install –save redux
b. 初始化目录结构
cd src
mkdir redux
cd redux
mkdir actions
mkdir reducers
touch reducers.js
touch store.js
touch actions/counter.js
touch reducers/counter.js

c. action 创建函数
export const INCREMENT = "counter/INCREMENT";
export const DECREMENT = "counter/DECREMENT";
export const RESET = "counter/RESET";
export function increment() {
 return {type: INCREMENT}
}
export function decrement() {
 return {type: DECREMENT}
}
export function reset() {
 return {type: RESET}
}
d. reducer函数

reducer是一个纯函数,接收action和旧的state,生成新的state。

import {INCREMENT, DECREMENT, RESET} from '../action/counter';
/*初始化state*/
const iniState = {
 count: 0
};

/*reducer*/
export default function reducer(state = initState, action) {
 switch(action.type) {
 case INCREMENT:
 return {
 count: state.count + 1
 };
 case DECREMENT:
 return {
 count: state.count - 1
 };
 case RESET:
 return {count: 0};
 default:
 return state
 }
}

在src/redux/reducers.js中把多个reducer整合起来

import counter from './reducers/counter';
export default function combineReducers(state={}, action){
 return {
 counter: counter(state.counter, action)
 }
}

无论是combineReducers函数也好,还是reducer函数也好,都是接收state和action,
返回更新后的state。区别就是combineReducers函数是处理整棵树,reducer函数是处理树的某一点。

e. 创建store

将reducer与action联系到一起。

import {createStore} from 'redux';
import combineReducers from './reducers.js';
let store = createStore(combineReducers);
export default store;

f. 测试redux
创建src/redux/testRedux.js
添加内容

import {increment, decrement, reset} from './action/counter';
import store from './store';
// 打印初始状态
console.log(store.getState());
// 每次state更新时,打印日志
// 注意subscribe()返回一个函数来注销监听器
let unsubscribe = store.subscribe(() =>
 console.log(store.getState())
 );
// 发起一系列action
store.dispatch(increment());
store.dispatch(decrement());
store.dispatch(reset());
// 停止监听state状态
unsubscribe();

在当前文件夹执行命令
webpack testRedux.js build.js
node build.js
查看输出结果:


1.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容