本文共 56163 字,大约阅读时间需要 187 分钟。
源码地址: 提问反馈:因为本教程写于2017年9月,然而前端技术发展太快了。有些库的版本一直在升级,所以你如果碰到奇怪的问题,请先检查下安装的库版本是否和我源码中的一样。please~大家阅读的时候,照着目录来阅读哦,有些章节不在文章里面。要点链接的~目录
写在前面 当我第一次跟着项目做 做项目,总是要解决各种问题的,所以每个地方都需要去了解,但是对整个框架没有一个整体的了解,实在是不行。 期间,我也跟着别人的搭建框架的教程一步一步的走,但是经常因为自己太菜,走不下去。在经过各种蹂躏之后,对整个框架也有一个大概的了解, 我就想把他写下来,让后来的菜鸟能跟着我的教程对react 全家桶有一个全面的认识。 我的这个教程,从新建根文件夹开始,到成型的框架,每个文件为什么要建立?建立了干什么?每个依赖都是干什么的?一步一步写下来,供大家学习。 当然,这个框架我以后会一直维护的,也希望大家能一起来完善这个框架,如果您有任何建议,欢迎在这里留言,欢迎 我基于该框架 说明
cd src/pagesmkdir Home
│ .babelrc #babel配置文件│ package-lock.json│ package.json│ README.MD│ webpack.config.js #webpack生产配置文件│ webpack.dev.config.js #webpack开发配置文件│ ├─dist├─public #公共资源文件└─src #项目源码 │ index.html #index.html模板 │ index.js #入口文件 │ ├─component #组建库 │ └─Hello │ Hello.js │ ├─pages #页面目录 │ ├─Counter │ │ Counter.js │ │ │ ├─Home │ │ Home.js │ │ │ ├─Page1 │ │ │ Page1.css #页面样式 │ │ │ Page1.js │ │ │ │ │ └─images #页面图片 │ │ brickpsert.jpg │ │ │ └─UserInfo │ UserInfo.js │ ├─redux │ │ reducers.js │ │ store.js │ │ │ ├─actions │ │ counter.js │ │ userInfo.js │ │ │ ├─middleware │ │ promiseMiddleware.js │ │ │ └─reducers │ counter.js │ userInfo.js │ └─router #路由文件 Bundle.js router.js init项目
webpack
babel
通俗的说,就是我们可以用ES6, ES7等来编写代码,Babel会把他们统统转为ES5。
{ "presets": [ "es2015", "react", "stage-0" ], "plugins": [] } 修改 /*src文件夹下面的以.js结尾的文件,要使用babel解析*/ /*cacheDirectory是用来缓存编译结果,下次编译加速*/ module: { rules: [{ test: /\.js$/, use: ['babel-loader?cacheDirectory=true'], include: path.join(__dirname, 'src') }] } 现在我们简单测试下,是否能正确转义ES6~ 修改src/index.js /*使用es6的箭头函数*/ var func = str => { document.getElementById('app').innerHTML = str; }; func('我现在在使用Babel!'); 执行打包命令 浏览器打开 然后我们打开打包后的 Q: A: 每一级包含上一级的功能,比如 参考地址: react
修改 import React from 'react';import ReactDom from 'react-dom';ReactDom.render( 执行打包命令 打开 我们简单做下改进,把 cd srcmkdir componentcd componentmkdir Hellocd Hellotouch Hello.js 按照React语法,写一个Hello组件 import React, {Component} from 'react';export default class Hello extends Component { render() { return ( 然后让我们修改
import React from 'react';import ReactDom from 'react-dom';import Hello from './component/Hello/Hello';ReactDom.render( 在根目录执行打包命令
打开 命令优化 Q:每次打包都得在根目录执行这么一长串命令 A:修改
"scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev-build": "webpack --config webpack.dev.config.js" } 现在我们打包只需要执行 参考地址: react-router
新建 cd srcmkdir router && touch router/router.js 按照
import React from 'react';import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';import Home from '../pages/Home/Home';import Page1 from '../pages/Page1/Page1';const getRouter = () => ( 新建页面文件夹 cd srcmkdir pages 新建两个页面 cd src/pagesmkdir Home && touch Home/Home.jsmkdir Page1 && touch Page1/Page1.js 填充内容:
import React, {Component} from 'react';export default class Home extends Component { render() { return ( Page1.js import React, {Component} from 'react';export default class Page1 extends Component { render() { return ( 现在路由和页面建好了,我们在入口文件 修改 import React from 'react';import ReactDom from 'react-dom';import getRouter from './router/router';ReactDom.render( getRouter(), document.getElementById('app')); 现在执行打包命令 那么问题来了~我们发现点击‘首页’和‘Page1’没有反应。不要惊慌,这是正常的。 我们之前一直用这个路径访问 http://localhost:3000 ~我们需要配置一个简单的WEB服务器,指向index.html ~有下面两种方法来实现
下一节,我们来使用第二种方法启动服务器。这一节的DEMO,先放这里。 参考地址 webpack-dev-server 简单来说,
修改
devServer: { contentBase: path.join(__dirname, './dist') } 现在执行
浏览器打开,OK,现在我们可以点击 react-router 已经成功了哦。 Q: A: 重要提示:webpack-dev-server编译后的文件,都存储在内存中,我们并不能看见的。你可以删除之前遗留的文件 每次执行 "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "dev-build": "webpack --config webpack.dev.config.js", "start": "webpack-dev-server --config webpack.dev.config.js" } 下次执行 既然用到了
proxy: { "/api": "http://localhost:3000" }
根据这几个配置,修改下我们的
devServer: { port: 8080, contentBase: path.join(__dirname, './dist'), historyApiFallback: true, host: '0.0.0.0' }
"start": "webpack-dev-server --config webpack.dev.config.js --color --progress" 现在我们执行 参考地址: 模块热替换(Hot Module Replacement) 到目前,当我们修改代码的时候,浏览器会自动刷新,不信你可以去试试。(如果你的不会刷新,看看这个) 我相信看这个教程的人,应该用过别人的框架。我们在修改代码的时候,浏览器不会刷新,只会更新自己修改的那一块。我们也要实现这个效果。 我们看下教程。 我们接下来要这么修改
"start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"
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')); 现在我们执行 做模块热替换,我们只改了几行代码,非常简单的。纸老虎一个~ 现在我需要说明下我们命令行使用的 --hot 吧。下面的方式我们知道一下就行,我们不用。同样的效果。 const webpack = require('webpack');devServer: { hot: true}plugins:[ new webpack.HotModuleReplacementPlugin()]
Node.js API 方式,就是建一个server.js 等等,网上大部分教程都是这种方式,这里不做讲解了。 你以为模块热替换到这里就结束了?nonono~ 上面的配置对 例如下面的 修改
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 ( 你可以测试一下,当我们修改代码的时候, 为了在 Q: 请问 A: 区别在于 react-hot-loader 在--hot 基础上做了额外的处理,来保证状态可以存下来。(来自) 下面我们来加入 安装依赖
根据, 我们要做如下几个修改~
{ "presets": [ "es2015", "react", "stage-0" ], "plugins": [ "react-hot-loader/babel" ]}
entry: [ 'react-hot-loader/patch', path.join(__dirname, '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', () => { const getRouter = require('./router/router').default; renderWithHotReload(getRouter()); });}function renderWithHotReload(RootElement) { ReactDom.render( 现在,执行 参考文章: 文件路径优化 做到这里,我们简单休息下。做下优化~ 在之前写的代码中,我们引用组件,或者页面时候,写的是相对路径~ 比如 import Home from '../pages/Home/Home'; webpack提供了一个别名配置,就是我们无论在哪个路径下,引用都可以这样 import Home from 'pages/Home/Home'; 下面我们来配置下,修改
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 Page1 from 'pages/Page1/Page1';
import getRouter from 'router/router'; 我们这里约定,下面,我们会默认配置需要的别名路径,不再做重复的讲述哦。 redux 接下来,我们就要就要就要集成 要对 如果要对 不要被各种关于 reducers, middleware, store 的演讲所蒙蔽 ---- Redux 实际是非常简单的。 当然,我这篇文章是写给新手的,如果看不懂上面的文章,或者不想看,没关系。先会用,多用用就知道原理了。 开始整代码!我们就做一个最简单的计数器。自增,自减,重置。 先安装 初始化目录结构 cd srcmkdir reduxcd reduxmkdir actionsmkdir reducerstouch reducers.jstouch store.jstouch actions/counter.jstouch reducers/counter.js 先来写 src/redux/actions/counter.js /*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}} 再来写
import {INCREMENT, DECREMENT, RESET} from '../actions/counter';/** 初始化state */const initState = { 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 }} 一个项目有很多的
import counter from './reducers/counter';export default function combineReducers(state = {}, action) { return { counter: counter(state.counter, action) }} 到这里,我们必须再理解下一句话。
看看上面的代码,无论是 state 。区别就是combineReducers 函数是处理整棵树,reducer 函数是处理树的某一点。 接下来,我们要创建一个 前面我们可以使用 还可以使用 那我们如何提交
import {createStore} from 'redux';import combineReducers from './reducers.js';let store = createStore(combineReducers);export default store; 到现在为止,我们已经可以使用 下面我们就简单的测试下 cd srccd reduxtouch testRedux.js
import {increment, decrement, reset} from './actions/counter';import store from './store';// 打印初始状态console.log(store.getState());// 每次 state 更新时,打印日志// 注意 subscribe() 返回一个函数用来注销监听器let unsubscribe = store.subscribe(() => console.log(store.getState()));// 发起一系列 actionstore.dispatch(increment());store.dispatch(decrement());store.dispatch(reset());// 停止监听 state 更新unsubscribe(); 当前文件夹执行命令 webpack testRedux.js build.jsnode build.js 是不是看到输出了 { counter: { count: 0 } }{ counter: { count: 1 } }{ counter: { count: 0 } }{ counter: { count: 0 } } 做这个测试,就是为了告诉大家, 到这里,我建议你再理下
就是酱紫~~ 这会
alias: { ... actions: path.join(__dirname, 'src/redux/actions'), reducers: path.join(__dirname, 'src/redux/reducers'), redux: path.join(__dirname, 'src/redux') } 把前面的相对路径都改改。 下面我们开始搭配 写一个 cd src/pagesmkdir Countertouch Counter/Counter.js
import React, {Component} from 'react';export default class Counter extends Component { render() { return ( 修改路由,增加
import React from 'react';import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';import Home from 'pages/Home/Home';import Page1 from 'pages/Page1/Page1';import Counter from 'pages/Counter/Counter';const getRouter = () => (
下一步,我们让 当然我们可以使用刚才测试
actions 的方法,转为Props 属性函数。 先来安装
import React, {Component} from 'react';import {increment, decrement, reset} from 'actions/counter';import {connect} from 'react-redux';class Counter extends Component { render() { return ( 下面我们要传入
import React from 'react';import ReactDom from 'react-dom';import {AppContainer} from 'react-hot-loader';import {Provider} from 'react-redux';import store from './redux/store';import getRouter from 'router/router';/*初始化*/renderWithHotReload(getRouter());/*热更新*/if (module.hot) { module.hot.accept('./router/router', () => { const getRouter = require('router/router').default; renderWithHotReload(getRouter()); });}function renderWithHotReload(RootElement) { ReactDom.render( 到这里我们就可以执行 但是你发现 ERROR in ./node_modules/react-redux/es/connect/mapDispatchToProps.jsModule not found: Error: Can't resolve 'redux' in 'F:\Project\react\react-family\node_modules\react-redux\es\connect'ERROR in ./src/redux/store.jsModule not found: Error: Can't resolve 'redux' in 'F:\Project\react\react-family\src\redux' WTF?这个错误困扰了半天。我说下为什么造成这个错误。我们引用
然而,我们在 resolve: { alias: { ... redux: path.join(__dirname, 'src/redux') } } 然后 redux 文件夹的地方改成相对路径哦。 现在你可以 这里我们再缕下(可以读)
接下来,我们要说异步 参考地址: 想象一下我们调用一个异步
下面,我们以向后台请求用户基本信息为例。
cd distmkdir apicd apitouch user.json
{ "name": "brickspert", "intro": "please give me a star"}
cd src/redux/actionstouch userInfo.js
export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST";export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS";export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL";function getUserInfoRequest() { return { type: GET_USER_INFO_REQUEST }}function getUserInfoSuccess(userInfo) { return { type: GET_USER_INFO_SUCCESS, userInfo: userInfo }}function getUserInfoFail() { return { type: GET_USER_INFO_FAIL }} 我们创建了请求中,请求成功,请求失败三个
再强调下, cd src/redux/reducerstouch userInfo.js
import {GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL} from 'actions/userInfo';const initState = { isLoading: false, userInfo: {}, errorMsg: ''};export default function reducer(state = initState, action) { switch (action.type) { case GET_USER_INFO_REQUEST: return { ...state, isLoading: true, userInfo: {}, errorMsg: '' }; case GET_USER_INFO_SUCCESS: return { ...state, isLoading: false, userInfo: action.userInfo, errorMsg: '' }; case GET_USER_INFO_FAIL: return { ...state, isLoading: false, userInfo: {}, errorMsg: '请求错误' }; default: return state; }} 这里的 组合
import counter from 'reducers/counter';import userInfo from 'reducers/userInfo';export default function combineReducers(state = {}, action) { return { counter: counter(state.counter, action), userInfo: userInfo(state.userInfo, action) }}
export function getUserInfo() { return function (dispatch) { dispatch(getUserInfoRequest()); return fetch('http://localhost:8080/api/user.json') .then((response => { return response.json() })) .then((json) => { dispatch(getUserInfoSuccess(json)) } ).catch( () => { dispatch(getUserInfoFail()); } ) }} 我们这里发现,别的 {type: xxxx} 但是我们现在的这个 为了让
这里涉及到 简单的说,中间件就是 action ,把他们转为标准的action 给reducer 。这是redux-thunk 的作用。 使用redux-thunk 中间件 我们来引入
import {createStore, applyMiddleware} from 'redux';import thunkMiddleware from 'redux-thunk';import combineReducers from './reducers.js';let store = createStore(combineReducers, applyMiddleware(thunkMiddleware));export default store; 到这里, cd src/pagesmkdir UserInfocd UserInfotouch UserInfo.js
import React, {Component} from 'react';import {connect} from 'react-redux';import {getUserInfo} from "actions/userInfo";class UserInfo extends Component { render() { const {userInfo, isLoading, errorMsg} = this.props.userInfo; return ( 这里你可能发现 增加路由 src/router/router.js import React from 'react';import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';import Home from 'pages/Home/Home';import Page1 from 'pages/Page1/Page1';import Counter from 'pages/Counter/Counter';import UserInfo from 'pages/UserInfo/UserInfo';const getRouter = () => ( 现在你可以执行 到这里 combinReducers优化
combinReducers 也是一样的。
import {combineReducers} from "redux";import counter from 'reducers/counter';import userInfo from 'reducers/userInfo';export default combineReducers({ counter, userInfo}); devtool优化 现在我们发现一个问题,代码哪里写错了,浏览器报错只报在 这让我们分析错误无从下手。看。 我们增加
devtool: 'inline-source-map' 这次看错误信息是不是提示的很详细了? 同时,我们在 编译css 先说这里为什么不用 scss 了,如果需要,自行编译哦。
{ test: /\.css$/, use: ['style-loader', 'css-loader']} 我们用 cd src/pages/Page1touch Page1.css
.page-box { border: 1px solid red;}
import React, {Component} from 'react';import './Page1.css';export default class Page1 extends Component { render() { return ( 好了,现在 编译图片
{ test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }]}
我们来用 cd src/pages/Page1mkdir images 给 修改代码,引用图片
import React, {Component} from 'react';import './Page1.css';import image from './images/brickpsert.jpg';export default class Page1 extends Component { render() { return ( 可以去看看效果啦。 按需加载 为什么要实现按需加载? 我们现在看到,打包完后,所有页面只生成了一个 如果每个页面都打包了自己单独的JS,在进入自己页面的时候才加载对应的js,那首屏加载就会快很多哦。 在 在4.0版本,官方放弃了这种处理按需加载的方式,选择了一个更加简洁的处理方式。 根据官方示例,我们开搞
cd src/routertouch Bundle.js
import React, {Component} from 'react'class Bundle extends Component { state = { // short for "module" but that's a keyword in js, so "mod" mod: null }; componentWillMount() { this.load(this.props) } componentWillReceiveProps(nextProps) { if (nextProps.load !== this.props.load) { this.load(nextProps) } } load(props) { this.setState({ mod: null }); props.load((mod) => { this.setState({ // handle both es imports and cjs mod: mod.default ? mod.default : mod }) }) } render() { return this.props.children(this.state.mod) }}export default Bundle;
import React from 'react';import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';import Bundle from './Bundle';import Home from 'bundle-loader?lazy&name=home!pages/Home/Home';import Page1 from 'bundle-loader?lazy&name=page1!pages/Page1/Page1';import Counter from 'bundle-loader?lazy&name=counter!pages/Counter/Counter';import UserInfo from 'bundle-loader?lazy&name=userInfo!pages/UserInfo/UserInfo';const Loading = function () { return 现在你可以 但是你可能发现,名字都是 我们修改下 output: { path: path.join(__dirname, './dist'), filename: 'bundle.js', chunkFilename: '[name].js' } 现在你运行发现名字变成 那么问题来了 其实在这里我们定义了,
看到没。这里有个 参考地址: 缓存 想象一下这个场景~ 我们网站上线了,用户第一次访问首页,下载了 这肯定不行呀,所以我们一般都会做一个缓存,用户下载一次 有一天,我们更新了 怎么解决?每次代码更新后,打包生成的名字不一样。比如第一次叫 文档 我们照着文档来
output: { path: path.join(__dirname, './dist'), filename: '[name].[hash].js', chunkFilename: '[name].[chunkhash].js' } 每次打包都用增加 现在我们试试,是不是修改了文件,打包后相应的文件名字就变啦? 但是你可能发现了,网页打开报错了~因为你 啊~那岂不是我每次编译打包,都得去改一下js名字?欲知后事如何,且看下节分享。 HtmlWebpackPlugin 这个插件,每次会自动把js插入到你的模板
新建模板 cd srctouch index.html
修改 var HtmlWebpackPlugin = require('html-webpack-plugin'); plugins: [new HtmlWebpackPlugin({ filename: 'index.html', template: path.join(__dirname, 'src/index.html') })],
说明一下: 提取公共代码 想象一下,我们的主文件,原来的 bundle.js 里面,每次项目发布,重新请求bundle.js 的时候,相当于重新请求了react 等这些公共库。浪费了~ 我们把
var webpack = require('webpack'); entry: { app: [ 'react-hot-loader/patch', path.join(__dirname, 'src/index.js') ], vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux'] } /*plugins*/ new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }) 把 但是你现在可能发现编译生成的文件 output: { path: path.join(__dirname, './dist'), filename: '[name].[hash].js', //这里应该用chunkhash替换hash chunkFilename: '[name].[chunkhash].js' } 但是无奈,如果用 现在我们在配置开发版配置文件,就向 生产坏境构建
文档 我们要开始做了~ touch webpack.config.js 在
const path = require('path');var HtmlWebpackPlugin = require('html-webpack-plugin');var webpack = require('webpack');module.exports = { devtool: 'cheap-module-source-map', entry: { app: [ path.join(__dirname, 'src/index.js') ], vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux'] }, output: { path: path.join(__dirname, './dist'), filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].js' }, module: { rules: [{ test: /\.js$/, use: ['babel-loader'], include: path.join(__dirname, 'src') }, { test: /\.css$/, use: ['style-loader', 'css-loader'] }, { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: path.join(__dirname, 'src/index.html') }), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }) ], resolve: { alias: { pages: path.join(__dirname, 'src/pages'), component: path.join(__dirname, 'src/component'), router: path.join(__dirname, 'src/router'), actions: path.join(__dirname, 'src/redux/actions'), reducers: path.join(__dirname, 'src/redux/reducers') } }}; 在
然后执行 接下来我们还是要优化正式版配置文件~ 文件压缩
const UglifyJSPlugin = require('uglifyjs-webpack-plugin')module.exports = { plugins: [ new UglifyJSPlugin() ]}
指定环境
module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('production') } }) ]}
优化缓存 刚才我们把 app.xxx.js 和vendor.xxx.js 不一样了哦。 但是现在又有一个问题了。 你随便修改代码一处,例如 vendor.xxx.js 名字也变了。这不行啊。这和没拆分不是一样一样了吗?我们本意是vendor.xxx.js 名字永久不变,一直缓存在用户本地的。~ 推荐了一个插件 plugins: [ new webpack.HashedModuleIdsPlugin() ] 现在你打包,修改代码再试试,是不是名字不变啦?错了,现在打包,我发现名字还是变了,经过比对文档,我发现还要加一个 new webpack.optimize.CommonsChunkPlugin({ name: 'runtime'}) 加上这句话就好了~为什么呢?看下。 注意,引入顺序在这里很重要。CommonsChunkPlugin 的 'vendor' 实例,必须在 'runtime' 实例之前引入。 public path 想象一个场景,我们的静态文件放在了单独的静态服务器上去了,那我们打包的时候,如何让静态文件的链接定位到静态服务器呢? 看文档
output: { publicPath : '/' } 打包优化 你现在打开
const CleanWebpackPlugin = require('clean-webpack-plugin');plugins: [ new CleanWebpackPlugin(['dist'])] 现在 抽取css 目前我们的 我们使用来实现。
const ExtractTextPlugin = require("extract-text-webpack-plugin");module.exports = { module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) } ] }, plugins: [ new ExtractTextPlugin({ filename: '[name].[contenthash:5].css', allChunks: true }) ]}
使用 先安装下
我们之前项目的一次API请求是这样写的哦~
export function getUserInfo() { return { types: [GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL], promise: client => client.get(`http://localhost:8080/api/user.json`) afterSuccess:(dispatch,getState,response)=>{ /*请求成功后执行的函数*/ }, otherData:otherData }} 然后在dispatch(getUserInfo())后,通过 中间件的教程看 我们想想中间件的逻辑
来写一个 cd src/reduxmkdir middlewarecd middlewaretouch promiseMiddleware.js
import axios from 'axios';export default store => next => action => { const {dispatch, getState} = store; /*如果dispatch来的是一个function,此处不做处理,直接进入下一级*/ if (typeof action === 'function') { action(dispatch, getState); return; } /*解析action*/ const { promise, types, afterSuccess, ...rest } = action; /*没有promise,证明不是想要发送ajax请求的,就直接进入下一步啦!*/ if (!action.promise) { return next(action); } /*解析types*/ const [REQUEST, SUCCESS, FAILURE] = types; /*开始请求的时候,发一个action*/ next({ ...rest, type: REQUEST }); /*定义请求成功时的方法*/ const onFulfilled = result => { next({ ...rest, result, type: SUCCESS }); if (afterSuccess) { afterSuccess(dispatch, getState, result); } }; /*定义请求失败时的方法*/ const onRejected = error => { next({ ...rest, error, type: FAILURE }); }; return promise(axios).then(onFulfilled, onRejected).catch(error => { console.error('MIDDLEWARE ERROR:', error); onRejected(error) })} 修改 import {createStore, applyMiddleware} from 'redux';import combineReducers from './reducers.js';import promiseMiddleware from './middleware/promiseMiddleware'let store = createStore(combineReducers, applyMiddleware(promiseMiddleware));export default store; 修改 export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST";export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS";export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL";export function getUserInfo() { return { types: [GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL], promise: client => client.get(`http://localhost:8080/api/user.json`) }} 是不是简单清新很多啦? 修改 case GET_USER_INFO_SUCCESS: return { ...state, isLoading: false, userInfo: action.result.data, errorMsg: '' };
调整文本编辑器 使用自动编译代码时,可能会在保存文件时遇到一些问题。某些编辑器具有“安全写入”功能,可能会影响重新编译。 要在一些常见的编辑器中禁用此功能,请查看以下列表:
合并提取 想象一个场景,现在我想给 这肯定不行啊。所以我们要把公共的配置文件提取出来。提取到
这里我们需要用到来合并公共配置和单独的配置。 这样说一下,应该看代码就能看懂了。下次公共配置直接就写在
const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');const webpack = require('webpack');commonConfig = { entry: { app: [ path.join(__dirname, 'src/index.js') ], vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux'] }, output: { path: path.join(__dirname, './dist'), filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].js', publicPath: "/" }, module: { rules: [{ test: /\.js$/, use: ['babel-loader?cacheDirectory=true'], include: path.join(__dirname, 'src') }, { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: path.join(__dirname, 'src/index.html') }), new webpack.HashedModuleIdsPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new webpack.optimize.CommonsChunkPlugin({ name: 'runtime' }) ], resolve: { alias: { pages: path.join(__dirname, 'src/pages'), components: path.join(__dirname, 'src/components'), router: path.join(__dirname, 'src/router'), actions: path.join(__dirname, 'src/redux/actions'), reducers: path.join(__dirname, 'src/redux/reducers') } }};module.exports = commonConfig;
const merge = require('webpack-merge');const path = require('path');const commonConfig = require('./webpack.common.config.js');const devConfig = { devtool: 'inline-source-map', entry: { app: [ 'react-hot-loader/patch', path.join(__dirname, 'src/index.js') ] }, output: { /*这里本来应该是[chunkhash]的,但是由于[chunkhash]和react-hot-loader不兼容。只能妥协*/ filename: '[name].[hash].js' }, module: { rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }] }, devServer: { contentBase: path.join(__dirname, './dist'), historyApiFallback: true, host: '0.0.0.0', }};module.exports = merge({ customizeArray(a, b, key) { /*entry.app不合并,全替换*/ if (key === 'entry.app') { return b; } return undefined; }})(commonConfig, devConfig);
const merge = require('webpack-merge');const webpack = require('webpack');const UglifyJSPlugin = require('uglifyjs-webpack-plugin');const CleanWebpackPlugin = require('clean-webpack-plugin');const ExtractTextPlugin = require("extract-text-webpack-plugin");const commonConfig = require('./webpack.common.config.js');const publicConfig = { devtool: 'cheap-module-source-map', module: { rules: [{ test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) }] }, plugins: [ new CleanWebpackPlugin(['dist/*.*']), new UglifyJSPlugin(), new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('production') } }), new ExtractTextPlugin({ filename: '[name].[contenthash:5].css', allChunks: true }) ]};module.exports = merge(commonConfig, publicConfig); 优化目录结构并增加404页面 现在我们优化下目录结构,把
import React, {Component} from 'react';import Nav from 'components/Nav/Nav';import getRouter from 'router/router';export default class App extends Component { render() { return (
import React from 'react';import ReactDom from 'react-dom';import {AppContainer} from 'react-hot-loader';import {Provider} from 'react-redux';import store from './redux/store';import {BrowserRouter as Router} from 'react-router-dom';import App from 'components/App/App';renderWithHotReload(App);if (module.hot) { module.hot.accept('components/App/App', () => { const NextApp = require('components/App/App').default; renderWithHotReload(NextApp); });}function renderWithHotReload(RootElement) { ReactDom.render(
import NotFound from 'bundle-loader?lazy&name=notFound!pages/NotFound/NotFound'; 加入 babel-plugin-transform-runtime 和 babel-polyfill
修改`.babelrc`配置文件,增加配置.babelrc```javascript "plugins": [ "transform-runtime" ]```
参考地址: 集成PostCSS Q: 这是啥?为什么要用它? 他有很多很多的插件,我们举几个例子~ 这个插件,可以自动给css属性加浏览器前缀。 /*编译前*/.container{ display: flex;}/*编译后*/.container{ display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex;} 允许你使用未来的 CSS 特性(包括 autoprefixer) 当然,它有很多很多的插件可以用,你可以去官网详细了解。我们今天只用 npm install --save-dev postcss-loadernpm install --save-dev postcss-cssnext 修改 webpack.dev.config.js rules: [{ test: /\.(css|scss)$/, use: ["style-loader", "css-loader", "postcss-loader"] }] webpack.config.js rules: [{ test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: ["css-loader", "postcss-loader"] }) }] 根目录增加 touch postcss.config.js
module.exports = { plugins: { 'postcss-cssnext': {} }}; 现在你运行代码,然后写个css,去浏览器审查元素,看看,属性是不是生成了浏览器前缀? redux 模块热替换配置 今天突然发现,当修改reducer代码的时候,页面会整个刷新,而不是局部刷新唉。 这不行,就去查了webpack文档,果然是要配置的。 代码修改起来也简单,增加一段监听reducers变化,并替换的代码。
if (module.hot) { module.hot.accept("./reducers", () => { const nextCombineReducers = require("./reducers").default; store.replaceReducer(nextCombineReducers); });} 哦了~ 模拟AJAX数据之Mock.js 每个改进都是为了解决问题。 现在我在开发中碰到了问题,我先描述下问题: 我们现在做前后端完全分离的应用,前端写前端的,后端写后端的,他们通过API接口连接。 前端同学心理路程:"后端同学接口写的好慢,我都没法调试了。" 是不是有这个问题呢?一般我们怎么解决? 第一种:自己这边随便造点数据,等后端接口写好了之后,再小修改,再调试。 第二种:想想我们之前获得用户信息的 并且,后端接口一般都不带 好了,下面介绍下今天的主角。 他会做一件事情:拦截AJAX请求,返回需要的数据! 我们写AJAX请求的时候,正常写,Mock.js会自动拦截的。 Mock.js提供各种随机生成数据。具体可以去官网看~ 下面我们就在项目中集成咯:
到这里还没完,我们还要配置:只有在开发坏境下,才引入 跟着我做: 先给
resolve: { alias: { ... mock: path.join(__dirname, 'mock') } }
const webpack = require('webpack'); plugins:[ new webpack.DefinePlugin({ MOCK: true }) ] 然后修改 if (MOCK) { require('mock/mock');} 这样,就只会在 哦了,到这里就结束了~回头缕下: 我们定义了mock,在index.js引入。 mock的工作就是,拦截AJAX请求,返回模拟数据。 参考文章: 使用 CSS Modules 关于什么是 可以去看阮一峰的文章 修改以下几个地方:
enjoy it! 使用 json-server 代替 Mock.js 和
我们用
let Mock = require('mockjs');var Random = Mock.Random;module.exports = function () { var data = {}; data.user = { 'name': Random.cname(), 'intro': Random.word(20) }; return data;};
"mock": "json-server mock/mock.js --watch --port 8090","mockdev": "npm run mock & npm start"
devServer: { ... proxy: { "/api/*": "http://localhost:8090/$1" } } 哦了,你可以 问题: 问题修复 1. react热模块加载无效举例:在首页中,当我们计数加上去,然后修改代码,计数又恢复成0了。也就是热模块加载的时候,重置了 如果我们不使用 解决问题参考这里: 解决步骤:
import {hot} from 'react-hot-loader';...export default hot(module)(Home); 其他模块如果需要,可以自己同理修改哦。 |
? 51? 1? 6? 17
Owner
edited
合并提取 想象一个场景,现在我想给 这肯定不行啊。所以我们要把公共的配置文件提取出来。提取到
这里我们需要用到来合并公共配置和单独的配置。 这样说一下,应该看代码就能看懂了。下次公共配置直接就写在
const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin');const webpack = require('webpack');commonConfig = { entry: { app: [ path.join(__dirname, 'src/index.js') ], vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux'] }, output: { path: path.join(__dirname, './dist'), filename: '[name].[chunkhash].js', chunkFilename: '[name].[chunkhash].js', publicPath: "/" }, module: { rules: [{ test: /\.js$/, use: ['babel-loader?cacheDirectory=true'], include: path.join(__dirname, 'src') }, { test: /\.(png|jpg|gif)$/, use: [{ loader: 'url-loader', options: { limit: 8192 } }] }] }, plugins: [ new HtmlWebpackPlugin({ filename: 'index.html', template: path.join(__dirname, 'src/index.html') }), new webpack.HashedModuleIdsPlugin(), new webpack.optimize.CommonsChunkPlugin({ name: 'vendor' }), new webpack.optimize.CommonsChunkPlugin({ name: 'runtime' }) ], resolve: { alias: { pages: path.join(__dirname, 'src/pages'), components: path.join(__dirname, 'src/components'), router: path.join(__dirname, 'src/router'), actions: path.join(__dirname, 'src/redux/actions'), reducers: path.join(__dirname, 'src/redux/reducers') } }};module.exports = commonConfig;
const merge = require('webpack-merge');const path = require('path');const commonConfig = require('./webpack.common.config.js');const devConfig = { devtool: 'inline-source-map', entry: { app: [ 'react-hot-loader/patch', path.join(__dirname, 'src/index.js') ] }, output: { /*这里本来应该是[chunkhash]的,但是由于[chunkhash]和react-hot-loader不兼容。只能妥协*/ filename: '[name].[hash].js' }, module: { rules: [{ test: /\.css$/, use: ["style-loader", "css-loader"] }] }, devServer: { contentBase: path.join(__dirname, './dist'), historyApiFallback: true, host: '0.0.0.0', }};module.exports = merge({ customizeArray(a, b, key) { /*entry.app不合并,全替换*/ if (key === 'entry.app') { return b; } return undefined; }})(commonConfig, devConfig);
const merge = require('webpack-merge');const webpack = require('webpack');const UglifyJSPlugin = require('uglifyjs-webpack-plugin');const CleanWebpackPlugin = require('clean-webpack-plugin');const ExtractTextPlugin = require("extract-text-webpack-plugin");const commonConfig = require('./webpack.common.config.js');const publicConfig = { devtool: 'cheap-module-source-map', module: { rules: [{ test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) }] }, plugins: [ new CleanWebpackPlugin(['dist/*.*']), new UglifyJSPlugin(), new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('production') } }), new ExtractTextPlugin({ filename: '[name].[contenthash:5].css', allChunks: true }) ]};module.exports = merge(commonConfig, publicConfig); |
原文
转载地址:http://ddwii.baihongyu.com/