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

2020-02-14 19:36栏目:龙竞技官网
TAG:

git clone git@github.com:xiehaitao0229/react-wepack4-xht.git``cd react-webpack4-xht

(12)react-redux

与上述手动编译,引入store不同,react-redux提供了一个方法connect

容器组件就是使用 store.subscribe() 从 Redux state 树中读取部分数据,并通过 props 来把这些数据提供给要渲染的组件。你可以手工来开发容器组件,但建议使用 React Redux 库的 connect() 方法来生成,这个方法做了性能优化来避免很多不必要的重复渲染。

参考链接:

`npm run dev` // 启动本地`npm run build` // 打包线上环境`npm run clean` // 清除线上环境打包出来的文件`npm run test` // 单元测试的工具库`npm run fix` // 修复eslint的写法`npm run format` // 格式化代码`npm run precommit` // commit 代码到git仓库的检查
a.安装react-redux

npm install --save react-redux

  • Redux中文文档
  • Redux 入门教程-阮一峰
  • 看漫画,学 Redux
  • 在react-native中使用redux
  • [React Native]Redux的基本使用方式
  • Redux管理复杂应用数据逻辑

webpack今年推出的4这个版本就一直关注很学习它,webpack4这个版本借鉴了parcel的零配置,打包速度变得更快,值得大家去跟进学习。

b.创建Counter组件

src/Counter/Counter.js中

import React, {Component} from 'react';

export default class Counter extends Component {
    render() {
        return (
            <div>
                <div>当前计数为(显示redux计数)</div>
                <button onClick={() => {
                    console.log('调用自增函数');
                }}>自增
                </button>
                <button onClick={() => {
                    console.log('调用自减函数');
                }}>自减
                </button>
                <button onClick={() => {
                    console.log('调用重置函数');
                }}>重置
                </button>
            </div>
        )
    }
}

修改路由,增加Counter,src/router/router.js中

+ import Counter from 'pages/Counter/Counter';
+ <li><Link to="/counter">Counter</Link></li>
+ <Route path="/counter" component={Counter}/>

npm start查看效果

目录

  • 应用场景
  • 使用的三原则
    • 单一数据源
    • 状态是只读的
    • 通过纯函数修改State
  • redux状态管理的流程及相关概念
    • store
    • Action
    • Action 创建函数(Action Creator)
    • Reducer
  • redux如何与组件结合
    • 具体示例1
    • 具体示例2

既然我们已经迎接了webpack4的到来了,那么就一起来使用一下,即使你没用过之前的版本,没关系,我们重新出发,将工作中常用到的配置写给大家来看

c .将Counter组件与Redux联合起来

使Counter能获得Redux的state,并且能发射action。与(11).f测试方法不同,这里使用react-redux提供的connect方法。
connect接收两个参数,一个mapStateToProps,就是把redux的state,转为组件的Props,还有一个参数是mapDispatchToprops,把发射actions的方法,转为Props属性函数。
优化路径:

alias {
     + actions: path.join(__dirname, 'src/redux/actions'),
     + reducers: path.join(__dirname, 'src/redux/reducers')
    }

注意:为了避免后面使用import {createStore} from ‘react-redux’冲突,因此我们不将redux写别名。
在src/index.js导入store,作为Counter组件的属性,如下:

import React from 'react';
import ReactDom from 'react-dom';
import {AppContainer} from 'react-hot-loader';
+ import {Provider} from 'react-redux';

import getRouter from 'router/router';
+ import store from 'redux/store';

/*初始化*/
// 如果没该步骤,页面会出现空白
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>
           <Provider store={store}>
               {RootElement}
           </Provider>
       </AppContainer>,
       document.getElementById('app')
   )
}

修改Counter.js

import React, {Component} from 'react';
import {increment, decrement, reset} from 'actions/counter';

import {connect} from 'react-redux';

 class Counter extends Component {
    render() {
        return (
            <div>
                <div>当前计数为{this.props.counter.count}</div>
                <button onClick={() => 
                    this.props.increment()
                }>自增
                </button>
                <button onClick={() => 
                    this.props.decrement()
                }>自减
                </button>
                <button onClick={() => 
                    this.props.reset()
                }>重置
                </button>
            </div>
        )
    }
}
const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
};
const mapDispatchToProps = (dispatch) => {
    return{
        increment: () => {
            dispatch(increment())
        },
        decrement: () => {
            dispatch(decrement())
        },
        reset: ()=> {
            dispatch(reset())
        }
    }
};
//
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

npm start

图片 1

2.png

总结:
(a)在store.js初始化 store,然后将 state 上的属性作为 props 在最外成组件中层层传递下去。
(b)在最外层容器中,把所有内容包裹在 Provider 组件中,将之前创建的 store 作为 prop 传给 Provider。
(c)Provider 内的任何一个组件,如果需要使用 state 中的数据,就必须是「被 connect 过的」组件——使用 connect 方法对「你编写的组件」进行包装后的产物。
(d)connet到底做了什么呢?
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
connect() 接收四个参数,它们分别是 mapStateToProps,mapDispatchToProps,mergeProps和options。
**mapStateToProps(state, ownProps) : stateProps **这个函数允许我们将 store 中的数据作为 props 绑定到组件上。

const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
};

获取我们需要的state,并转为props,所以<Counter>会有一个名为counter的属性。也可以将state.counter进行筛选后再动态输出。
函数的第二个参数 ownProps,是 组件自己的props。
** mapDispatchToProps(dispatch, ownProps): dispatchProps** 将action作为props绑定到组件上。

const mapDispatchToProps = (dispatch) => {
    return{
        increment: () => {
            dispatch(increment())
        },
        decrement: () => {
            dispatch(decrement())
        },
        reset: ()=> {
            dispatch(reset())
        }
    }
};

每当我们在 store 上 dispatch 一个 action,store 内的数据就会相应地发生变化。
同时,Redux 本身提供了 bindActionCreators 函数,来将 action 包装成直接可被调用的函数

import {bindActionCreators} from 'redux';

const mapDispatchToProps = (dispatch, ownProps) => {
  return bindActionCreators({
    increase: action.increase,
    decrease: action.decrease
  });
}

不管是 stateProps 还是 dispatchProps,都需要和 ownProps merge 之后才会被赋给 MyComp。connect 的第三个参数就是用来做这件事。通常情况下,你可以不传这个参数,connect 就会使用 Object.assign 替代该方法。

应用场景

React设计理念之一为单向数据流,这从一方面方便了数据的管理。但是React本身只是view,并没有提供完备的数据管理方案。随着应用的不断复杂化,如果用react构建前端应用的话,就要应对纷繁复杂的数据通信和管理,js需要维护更多的状态(state),这些state可能包括用户信息、缓存数据、全局设置状态、被激活的路由、被选中的标签、是否加载动效或者分页器等等。

这时,Flux架构应运而生,Redux是其最优雅的实现,Redux是一个不依赖任何库的框架,但是与react结合的最好,其中react-redux等开源组件便是把react与redux组合起来进行调用开发。

备注:

1.如果你不知道是否需要 Redux,那就是不需要它

2.只有遇到 React 实在解决不了的问题,你才需要 Redux

Redux使用场景:

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

比如,论坛应用中的夜间设置、回到顶部、userInfo全局共享等场景。redux最终目的就是让状态(state)变化变得可预测.

  • 需要先在项目中npm init初始化一下,生成package.json
  • 建议node版本安装到8.2以上
(13)action异步

当调用异步 API 时,有两个非常关键的时刻:发起请求的时刻,和接收到响应的时刻(也可能是超时)。

这两个时刻都可能会更改应用的 state;为此,你需要 dispatch 普通的同步 action。一般情况下,每个 API 请求都需要dispatch 至少三种 action:

①一种通知reducer请求开始的action:

对于这种 action,reducer 可能会切换一下state中的isFetching 标记。以此来告诉 UI 来显示加载界面。

②一种通知reducer请求成功的action。

对于这种 action,reducer 可能会把接收到的新数据合并到state 中,并重置 isFetching。UI 则会隐藏加载界面,并显示接收到的数据。

③一种通知 reducer 请求失败的action。

对于这种 action,reducer 可能会重置isFetching。另外,有些 reducer 会保存这些失败信息,并在 UI 里显示出来。

为了区分这三种 action,可能在 action 里添加一个专门的 status 字段作为标记位,又或者为它们定义不同的 type。

{ type: 'FETCH_POSTS_REQUEST' }

{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }

{ type: 'FETCH_POSTS_SUCCESS', response: { ... } }

该实例的逻辑思路如下:

i. 请求开始的时候,界面转圈提示正在加载。isLoading置为true。

ii. 请求成功,显示数据。isLoading置为false,data填充数据。

iii. 请求失败,显示失败。isLoading置为false,显示错误信息。

使用的三原则

  • 单一数据源

整个应用的state,存储在唯一一个object中,同时也只有一个store用于存储这个object.

  • 状态是只读的

唯一能改变state的方法,就是触发action操作。action是用来描述正在发生的事件的一个对象

  • 通过纯函数修改State

纯函数的问题,也是来自于函数式编程思想,我们在中学时学的函数就是纯函数,对于同一个输入,必然有相同的输出。这就保证了数据的可控性,这里的纯函数就是reducer

// webpack4中除了正常安装webpack之外,需要再单独安一个webpack-clinpm i webpack webpack-cli -D
a.创建后台API

创建一个user.json,等会请求用,相当于后台的API接口。在dist/api/user.json文件下:

{

 "name": "LALA",

 "intro": "enjoy hotpot"

}

redux状态管理的流程及相关概念

图片 2

image

  • store

Store 就是保存数据的地方,保存着本程序所有的redux管理的数据,你可以把它看成一个容器。整个应用只能有一个 Store。(一个store是一个对象, reducer会改变store中的某些值)

Redux 提供createStore这个函数,用来生成 Store。

import { createStore } from 'redux';
const store = createStore(fn);

上面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。这个fn就是reducer纯函数,通常我们在开发中也会使用中间件,来优化架构,比如最常用的异步操作插件,redux-thunk,如果配合redux-thunk来创建store的话,代码示例:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootReudcer';

let createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
let store = createStoreWithMiddleware(rootReducer);

redux-thunk的源码及其简单,如下:

// 判断action是否是函数,如果是,继续执行递归式的操作。所以在redux中的异步,只能出现在action中,而且还需要有中间件的支持。
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

同步action与异步action最大的区别是:

同步只返回一个普通action对象。而异步操作中途会返回一个promise函数。当然在promise函数处理完毕后也会返回一个普通action对象。thunk中间件就是判断如果返回的是函数,则不传导给reducer,直到检测到是普通action对象,才交由reducer处理。


Store 有以下职责:

  • 提供 getState() 方法获取 state;
  • 提供 dispatch(action) 方法更新 state;
  • 通过 subscribe(listener) 注册监听器;
  • 通过 subscribe(listener) 返回的函数注销监听器。

一般情况下,我们只需要getState()和dispatch()方法即可,即可以解决绝大部分问题。

我们可以自定义中间件

比如我们自定义一个可以打印出当前的触发的action以及出发后的state变化的中间件,代码改动如下:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from '../reducers/rootReudcer';

let logger = store => next => action => {
    if(typeof action === 'function') {
        console.log('dispatching a function')
    }else{
        console.log('dispatching', action);
    }

    let result = next(action);
    // getState() 可以拿到store的状态, 也就是所有数据
    console.log('next state', store.getState());
    return result;
}

let middleWares = {
    logger, 
    thunk
}
// ... 扩展运算符
let createStoreWithMiddleware = applyMiddleware(...middleWares)(createStore);

let store = createStoreWithMiddleware(rootReducer);

补充:我们自定义的中间件,也有对应的开源插件,redux-logger,人家的更厉害。

如果,app中涉及到登录问题的时候,可以使用redux-persist第三方插件,这个第三方插件来将store对象存储到本地,以及从本地恢复数据到store中,比如说保存登录信息,下次打开应用可以直接跳过登录界面,因为我们目前的应用属于内嵌程序,不登陆的话也进不来,所以不用它。

  • Action

Action 是一个对象,描述了触发的动作,仅此而已。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。通常它长一下这个样子:

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

除了 type 字段外,action 对象的结构完全由你自己决定,来看一个复杂点的:

{
    type: 'SET_SCREEN_LAST_REFRESH_TIME',
    screenId,
    lastRefreshTime,
    objectId
}

通常我们会添加一个新的模块文件来存储这些actions types,比如我们新建一个actionTypes.js来保存:

//主页actions
export const FETCH_HOME_LIST = 'FETCH_HOME_LIST';
export const RECEIVE_HOME_LIST = 'RECEIVE_HOME_LIST';
//分类页actions
export const FETCH_CLASS_LIST = 'FETCH_CLASS_LIST';
export const RECEIVE_CLASS_LIST = 'RECEIVE_CLASS_LIST';
//分类详细页actions
export const FETCH_CLASSDITAL_LIST = 'FETCH_CLASSDITAL_LIST';
export const RECEIVE_CLASSDITAL_LIST = 'RECEIVE_CLASSDITAL_LIST';
export const RESET_CLASSDITAL_STATE = 'RESET_CLASSDITAL_STATE'; 
// 设置页actions
export const CHANGE_SET_SWITCH = 'CHANGE_SET_SWITCH';
export const CHANGE_SET_TEXT = 'CHANGE_SET_TEXT';
// 用户信息
export const USER_INFO = 'USER_INFO';

引用的时候,可以:

import * as types from './actionTypes';
  • Action 创建函数(Action Creator)

Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。在 Redux 中的 action 创建函数只是简单的返回一个 action。

import * as types from './actionTypes';
// 设置详情页内容文字主题
let changeText = (theme) => {
    return {
        type: types.CHANGE_SET_TEXT,
        theme
    }
}   

// 函数changeText就是一个简单的action creator。

完整的action文件(setAction.js)

import * as types from './actionTypes';

let setTitle = (value) => {
    return (dispatch, getState) => {
        dispatch(changeValue(value))
    }
}

let setText = (text) => {
    return dispatch => {
        dispatch(changeText(text))
    }
}

// 修改标题颜色主题
let changeValue = (titleTheme) => {
    return {
        type: types.CHANGE_SET_SWITCH,
        titleTheme
    }
}

// 设置详情页内容文字颜色
let changeText = (textColor) => {
    return {
        type: types.CHANGE_SET_TEXT,
        textColor
    }
}

export {
    setText,
    setTitle
};

可以看到上述setTitle、setText函数,返回的并不是一个action对象,而是返回了一个函数,这个默认redux是没法处理的,这就需要使用中间件处理了,redux-thunk中间件用于处理返回函数的函数,上面也介绍了redux-thunk的使用基本方式。

  • Reducer

Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

函数签名:

(previousState, action) => newState

Reducer必须保持绝对纯净,永远不要在 reducer 里做这些操作:

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random();

完整的Reducer方法,(setReducer.js):

import * as types from '../actions/actionTypes';

const initialState = {
    titleTheme: false,
    textColor: false
}
// 这里一个技巧是使用 ES6 参数默认值语法 来精简代码
let setReducer = (state = initialState, action) => {

    switch(action.type){
        case types.CHANGE_SET_SWITCH:
            return Object.assign({}, state, {
                titleTheme: action.titleTheme,
            })

        case types.CHANGE_SET_TEXT:
            return Object.assign({}, state, {
                textColor: action.textColor
            })

        default:
            return state;
    }
}

export default setReducer

注意:

  • 不要修改 state。 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, {
    titleTheme: action.titleTheme,
    }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用 { ...state, ...newState } 达到相同的目的。
  • 在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state

关于拆分Reducer

这里只是举例了一个简单的功能的reducer,如果有不同的功能,需要设计很多reducer方法,注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

比如我们这个项目的reducer文件结构:

图片 3

image.png

其中rootReducer.js就是一个根reducer文件,使用了Redux 的 combineReducers() 工具类来进行封装整合。

/**
 * rootReducer.js
 * 根reducer
 */
import { combineReducers } from 'redux';
import Home from './homeReducer';
import Class from './classReducer';
import ClassDetial from './classDetialReducer';
import setReducer from './setReducer';
import userReducer from './userReducer';

export default rootReducer = combineReducers({
    Home,
    Class,
    ClassDetial,
    setReducer,
    userReducer,
})

这样根据这个根reducer,可以生成store,请看上文store的创建过程。

在项目下创建一个webpack.config.js文件来配置webpack

b.创建action
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";

// 创建请求中,请求成功,请求失败 三个action创建函数

function getUserInfoRequest() {

 return {

 type: GET_USER_INFO_REQUEST

 }

}

function getUserInfoSuccess() {

 return {

 type: GET_USER_INFO_SUCCESS,

 userInfo: userInfo

 // userInfo: userInfo???有什么作用呢?

 }

}

function getUserInfoFail() {

 return {

 type: GET_USER_INFO_FAIL

 }

}

// 将三个action与网络请求联系到一起

// 为什么要将网络请求放到action,而不是rreducer???

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());

 }

 )

 }

}

因为这个地方的action返回的是个函数,而不是对象,所以需要使用redux-thunk。

redux如何与组件结合

以上部分介绍了Redux 涉及的基本概念,下面介绍与组件交互的工作流程。

梳理一下Redux的工作流程:

图片 4

image

1.首先,用户发出 Action。

store.dispatch(action);

2.Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。

let nextState = todoApp(previousState, action);

3.state一旦有变化,Store就会调用监听函数,组件可以感知state的变化,更新View。

let newState = store.getState();
component.setState(newState);

具体示例1:

图片 5

fsdf.gif

设置页面有个switch按钮,可以全局设置标题栏的主题。

module.exports = { entry: '', // 入口文件 output: {}, // 出口文件 module: {}, // 处理对应模块 plugins: [], // 对应的插件 devServer: {}, // 开发服务器配置 mode: 'development' // 模式配置}
c.创建reducer

在src/redux/reducers/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.tyoe) {

 case GET_USER_INFO_REQUEST:

 return {

 // ...state 保证其他state更新;是和别人的Object.assign()起同一个作用

 ...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;

 }

}
代码拆分:

1.设置按钮所在组件:

// SetContainer.js

import React from 'react';
import {connect} from 'react-redux';
import SetPage from '../pages/SetPage';

class SetContainer extends React.Component {
    render() {
        return (
            <SetPage {...this.props} />
        )
    }
}

export default connect((state) => {

    const { setReducer } = state;
    return {
        setReducer
    }

})(SetContainer);

这是容器组件,将SetPage组件与redux结合起来,其中最重要的方法是connect,这个示例中是将setReducer作为属性传给SetPage组件,关于connect的详解,请移步到connect()。

2.SetPage组件

import React, {
    Component
} from 'react';
import {
    StyleSheet,
    Text,
    Image,
    ListView,
    TouchableOpacity,
    View,
    Switch,
    InteractionManager,
} from 'react-native';

import Common from '../common/common';
import Loading from '../common/Loading';
import HeaderView from '../common/HeaderView';

import {setText,setTitle} from '../actions/setAction';

export default class SetPage extends Component {
    constructor(props){
        super(props);
        this.state = {
            switchValue: false,
            textValue: false
        }

        this.onValueChange = this.onValueChange.bind(this);
        this.onTextChange = this.onTextChange.bind(this);
    }

    componentDidMount() {
        // console.log(this.props)
    }

    onValueChange(bool) {
        const { dispatch } = this.props;
        this.setState({
            switchValue: bool
        })
        dispatch(setTitle(bool));
    }

    onTextChange(bool) {
        const { dispatch } = this.props;

        this.setState({
            textValue: bool
        });

        dispatch(setText(bool));
    }

    render() {
        return (
            <View>
                <HeaderView
                  titleView= {'设置'}
                  />

                <View>
                    <View style={styles.itemContainer}>
                        <Text style={{fontSize: 16}}>全局设置标题主题</Text>
                        <Switch 
                            onValueChange={this.onValueChange}
                            value={this.state.switchValue}
                        />
                    </View>

                    <View style={styles.itemContainer}>
                        <Text style={{fontSize: 16}}>设置详情页文字主题</Text>
                        <Switch 
                            onValueChange={this.onTextChange}
                            value={this.state.textValue}
                        />
                    </View>
                </View>
            </View>
        )
    }
}

const styles = StyleSheet.create({
    itemContainer:{
        paddingLeft: 20,
        paddingRight: 20,
        height: 40,
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center'
    }
})

可以只看全局设置标题主题这个方法,设置详情页文字颜色和他同理。这里可以清晰的看到,用户切换主题switch按钮的时候,触发的方法:

dispatch(setTitle(bool));

3.我们查看一下setTitle这个action的源码:

// setAction.js
import * as types from './actionTypes';

let setTitle = (value) => {
    return (dispatch, getState) => {
        dispatch(changeValue(value))
    }
}

let setText = (text) => {
    return dispatch => {
        dispatch(changeText(text))
    }
}

// 修改标题主题
let changeValue = (titleTheme) => {
    return {
        type: types.CHANGE_SET_SWITCH,
        // 这里将titleTheme状态返回
        titleTheme
    }
}

// 设置详情页内容文字主题
let changeText = (textColor) => {
    return {
        type: types.CHANGE_SET_TEXT,
        textColor
    }
}

export {
    setText,
    setTitle
};

4.action只是负责发送事件,并不会返回一个新的state供页面组件调用,它是在reducer中返回的:

// setReducer.js

import * as types from '../actions/actionTypes';

const initialState = {
    titleTheme: false,
    textColor: false
}

let setReducer = (state = initialState, action) => {

    switch(action.type){
        case types.CHANGE_SET_SWITCH:
            return Object.assign({}, state, {
                titleTheme: action.titleTheme,
            })

        case types.CHANGE_SET_TEXT:
            return Object.assign({}, state, {
                textColor: action.textColor
            })

        default:
            return state;
    }
}

export default setReducer

最简单的reducer,就是根据初始值和action对象,返回一个新的state,提供给store,这样,页面里可以从store中获取到这些全局的state,用于更新组件。

我们只是写了怎样发送action和接收action发出newState的,下面来看这个标题组件是怎样和redux结合的。

5.HeaderView组件

/**
 * Created by ljunb on 16/5/8.
 * 导航栏标题
 */
import React from 'react';
import {
    StyleSheet,
    View,
    Text,
    Image,
    TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome';
import Common from '../common/common';
import {connect} from 'react-redux';

class HeaderView extends React.Component {

    constructor(props){
        super(props);

        this.state = {

        }
    }

    render() {
        // 这里,在这里
        const { titleTheme } = this.props.setReducer;
        let NavigationBar = [];

        // 左边图片按钮
        if (this.props.leftIcon != undefined) {
            NavigationBar.push(
                <TouchableOpacity
                    key={'leftIcon'}
                    activeOpacity={0.75}
                    style={styles.leftIcon}
                    onPress={this.props.leftIconAction}
                    >
                    <Icon color="black" size={30} name={this.props.leftIcon}/>
                </TouchableOpacity>
            )
        }

        // 标题
        if (this.props.title != undefined) {
            NavigationBar.push(
                <Text key={'title'} style={styles.title}>{this.props.title}</Text>
            )
        }

        // 自定义标题View
        if (this.props.titleView != undefined) {
            let Component = this.props.titleView;

            NavigationBar.push(
                <Text key={'titleView'} style={[styles.titleView, {color: titleTheme ? '#FFF' : '#000'}]}>{this.props.titleView}</Text>
            )
        }


        return (
            <View style={[styles.navigationBarContainer, {backgroundColor: titleTheme ? 'blue' : '#fff'}]}>
                {NavigationBar}
            </View>
        )
    }
}

const styles = StyleSheet.create({

    navigationBarContainer: {
        marginTop: 20,
        flexDirection: 'row',
        height: 44,
        justifyContent: 'center',
        alignItems: 'center',
        borderBottomColor: '#ccc',
        borderBottomWidth: 0.5,
        backgroundColor: 'white'
    },

    title: {
        fontSize: 15,
        marginLeft: 15,
    },
    titleView: {
        fontSize: 15,
    },
    leftIcon: {
       left: -Common.window.width/2+40,
    },
})


export default connect((state) => {

    const { setReducer } = state;
    return {
        setReducer
    }

})(HeaderView);

这个组件同样利用connect方法绑定了redux,变成了容器组件(container component)。

connect真的很关键,请详细查看官方文档,上面有链接。

其他不相关的内容忽略,核心代码是:

// 拿到全局的state 当有变化的时候,会马上修改
const { titleTheme } = this.props.setReducer;

具体示例2:

图片 6

image.png

利用redux来请求数据、下拉刷新、上拉加载更多。

1.首先,封装action。

import * as types from './actionTypes';
import Util from '../common/utils'; 
// action创建函数,此处是渲染首页的各种图片
export let home = (tag, offest, limit, isLoadMore, isRefreshing, isLoading) => {
    let URL = 'http://api.huaban.com/fm/wallpaper/pins?limit=';
    if (limit) URL += limit;
    offest ? URL += '&max=' + offest : URL += '&max=';
    tag ? URL += '&tag=' + encode_utf8(tag) : URL += '&tag='

    return dispatch => {
        // 分发事件  不修改状态   action是 store 数据的唯一来源。
        dispatch(feachHomeList(isLoadMore, isRefreshing, isLoading));
        return Util.get(URL, (response) => {
            // 请求数据成功后
            dispatch(receiveHomeList(response.pins))
        }, (error) => {
            // 请求失败
            dispatch(receiveHomeList([]));
        });

    }

}

function encode_utf8(s) {
    return encodeURIComponent(s);
}

// 我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
let feachHomeList = (isLoadMore, isRefreshing, isLoading) => {
    return {
        type: types.FETCH_HOME_LIST,
        isLoadMore: isLoadMore,
        isRefreshing: isRefreshing,
        isLoading: isLoading,
    }
}

let receiveHomeList = (homeList) => {
    return {
        type: types.RECEIVE_HOME_LIST,
        homeList: homeList,
    }
}
  • feachHomeList表示正在请求数据的动作;
  • receiveHomeList表示请求数据完后的动作;
  • dispatch(feachHomeList(isLoadMore, isRefreshing, isLoading));表示分发请求数据的动作;

2.封装reducer函数

import * as types from '../actions/actionTypes';
// 设置初始状态
const initialState = {
    HomeList: [],
    isLoading: true,
    isLoadMore: false,
    isRefreshing: false,
};

let homeReducer = (state = initialState, action) => {

    switch (action.type) {
        case types.FETCH_HOME_LIST:
            return Object.assign({}, state, {
                isLoadMore: action.isLoadMore,
                isRefreshing: action.isRefreshing,
                isLoading: action.isLoading
            })

        case types.RECEIVE_HOME_LIST:
            // 如果请求成功后,返回状态给组件更新数据
            return Object.assign({}, state, {
            // 如果是正在加载更多,那么合并数据
                HomeList: state.isLoadMore ? state.HomeList.concat(action.homeList) : action.homeList,
                isRefreshing: false,
                isLoading: false,
            })

        case types.RESET_STATE: // 清除数据
            return Object.assign({},state,{
                HomeList:[],
                isLoading:true,
            })
        default:
            return state;
    }
}

export default homeReducer;
  • 这里并没有处理没有更多数据的情况。

3.容器组件

import React from 'react';
import {connect} from 'react-redux';
import Home from '../pages/Home';

class HomeContainer extends React.Component {
    render() {
        return (
            <Home {...this.props} />
        )
    }
}

export default connect((state) => {
    const { Home } = state;
    return {
        Home
    }
})(HomeContainer);
  • 这里主要是利用connect函数将Home state绑定到Home组件中,并作为它的props;

4.UI组件

  • 组件挂载请求数据
...
let limit = 21;
let offest = '';
let tag = '';
let isLoadMore = false;
let isRefreshing = false;
let isLoading = true;
...
componentDidMount() {
    InteractionManager.runAfterInteractions(() => {
      const {dispatch} = this.props;
      // 触发action 请求数据
      dispatch(home(tag, offest, limit, isLoadMore, isRefreshing, isLoading));
    })
}
...
  • 下拉刷新
// 下拉刷新
  _onRefresh() {
    if (isLoadMore) {
      const {dispatch, Home} = this.props;
      isLoadMore = false;
      isRefreshing = true;
      dispatch(home('', '', limit, isLoadMore, isRefreshing, isLoading));
    }
  }
  • 上拉加载更多
// 上拉加载
  _onEndReach() {

    InteractionManager.runAfterInteractions(() => {
      const {dispatch, Home} = this.props;
      let homeList = Home.HomeList;
      isLoadMore = true;
      isLoading = false;
      isRefreshing = false;
      offest = homeList[homeList.length - 1].seq
      dispatch(home(tag, offest, limit, isLoadMore, isRefreshing, isLoading));
    })

  }
  • render方法
render() {
    // 这里可以拿到Home状态
    const { Home,rowDate } = this.props;
     tag = rowDate;

    let homeList = Home.HomeList;
    let titleName = '最新';
    return (
      <View>
        <HeaderView
          titleView= {titleName}
          leftIcon={tag ? 'angle-left' : null}
          />
        {Home.isLoading ? <Loading /> :
          <ListView
            dataSource={this.state.dataSource.cloneWithRows(homeList) }
            renderRow={this._renderRow}
            contentContainerStyle={styles.list}
            enableEmptySections={true}
            initialListSize= {10}
            onScroll={this._onScroll}
            onEndReached={this._onEndReach.bind(this) }
            onEndReachedThreshold={10}
            renderFooter={this._renderFooter.bind(this) }
            style={styles.listView}
            refreshControl={
              <RefreshControl
                refreshing={Home.isRefreshing}
                onRefresh={this._onRefresh.bind(this) }
                title="正在加载中……"
                color="#ccc"
                />
            }
            />
        }
      </View>

    );

  }

至此,一个简单的Reducer程序完成了,我们稍微总结一下:

  • 整个应用只有一个store,用来保存所有的状态,视图不需要自己维护状态。
  • 视图通过connect函数绑定到store,当store状态变化后,store会通知视图刷新。
  • 触发一个action之后,会经过可能N个reducers处理,最后根reducer会将所有reducers处理之后的状态合并,然后交给store,store再通知视图刷新。

本文的源码地址: 案例Demo

以上就是webpack的正常配置模块启动devServer需要安装一下webpack-dev-server

d. 合并reducer

redux/reducer.js

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)

 }

}
npm i webpack-dev-server -D
e. redux-thunk

redux中间件middleware就是action在到达reducer,先经过中间件处理。我们之前知道reducer能处理的action只有这样的{type:xxx},所以我们使用中间件来处理函数形式的action,把他们转为标准的action给reducer。这是redux-thunk的作用

图片 7image.png

f. 安装redux-thunk

npm install --save redux-thunk

接下来我们按照项目的结构,我们就从0开始去写一下配置

g. 在src/redux/store.js中引入middleware
+ import {createStore, applyMidddleware} from 'redux';

import combineReducers from './reducers.js';

+ import thunkMiddleware from 'redux-thunk';

+ let store = createStore(combineReducers,applyMidddleware(thunkMiddleware));

export default store;
// webpack.config.jsconst path = require;module.exports = { entry: './src/index.js', // 入口文件 output: { filename: 'bundle.js', // 打包后的文件名称 path: path.resolve // 打包后的目录,必须是绝对路径 }}
h. 创建UserInfo组件验证

src/pages/ UserInfo /UserInfo.js

import React, {Component} from 'react';

import {connect} from 'react-redux';

import {getUserInfo} from 'action/userInfo';

class UserInfo extends Component {

 render() {

 const {userInfo, isLoading, errorMsg} = this.props.userInfo;

 return(

 <div>

 {

 isLoading ? '请求信息中.......' :

 (

 errorMsg ? errorMsg :

 <div>

 <p>用户信息:</p>

 <p>用户名:{userInfo.name}</p>

 <p>介绍:{userInfo.intro}</p>

 </div>

 )

 }

 <button onClick={() => this.props.getUserInfo()}>请求用户信息</button>

 </div>

 )

 }

}

export default connect((state) => ({userInfo: state.userInfo}), {getUserInfo})(UserInfo);

上面就可以说是实现了最简单的webpack配置了,那接下来就打包一下看看

i. 添加路由/userInfo

在 src/router/router.js 文件下

+ import UserInfo from 'pages/UserInfo/UserInfo';

+ <li><Link to="/userInfo">UserInfo</Link></li>

+ <Route path="/userInfo" component={UserInfo}/>

npm start,查看效果

图片 8

3.png

图片 9image.png

d. 修改webpack的output属性
    output: {
    path: path.join(__dirname, 'dist'),
    filename: 'app.js',
     chunkFilename: '[name].js'
  }

图片 10

4.png

此时的文件名由:

import Home from 'bundle-loader?lazy&name=home!pages/Home/Home';

中的name值决定

工作当中我们打包编译的时候一般都执行npm run dev这样的命令,既然是通过npm执行的命令,我们就应该找到package.json里的执行脚本去配置一下命令,这里如下图所示

(20)webpack缓存

为了避免多次访问同一页面后不再次下载资源,需要进行缓存。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。因此,可以使用webpack配置,通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。

  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js',
    chunkFilename: '[name].[chunkhash].js'
  },

打包后的文件:

图片 11

6.png

但是在dist/index.html中引用的还是之前的filename: app.js ,访问时会出错。因此,可以用插件HtmlWebpackPlugin

图片 12image.png

(21)HtmlWebpackPlugin

HtmlWebpackPlugin插件会自动生成一个HTML文件,并引用相关的文件。

npm run build就是我们打包后的文件,这是生产环境下,上线需要的文件

a. 安装

npm install --save-dev html-webpack-plugin

npm run dev是我们开发环境下打包的文件,当然由于devServer帮我们把文件放到内存中了,所以并不会输出打包后的dist文件夹

b. webpack配置
+let HtmlWebpackPlugin = require('html-webpack-plugin'); //通过 npm 安装
+  plugins: [new HtmlWebpackPlugin({
        filename: 'index.html',
        template: path.join(__dirname, 'src/index.html')
    })],

文件都打包好了,但是我们在使用的时候不能在dist目录下去创建一个html文件,然后去引用打包后的js吧,这不合理,实际开发中也不会这样我们需要实现html打包功能,可以通过一个模板实现打包出引用好路径的html来这就需要用到一个常用的插件了,< html-webpack-plugin >,用之前我们来安一下它

c.创建index.html

删除 dist/index.html,创建src/html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Data</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

此时我们会发现

图片 13

image.png

找到

图片 14

我们发现之前在dist/index.html中的<script>仍然存在,这是为什么呢???

npm i html-webpack-plugin -D

let path = require;// 插件都是一个类,所以我们命名的时候尽量用大写开头let HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { entry: './src/index.js', output: { // 添加hash可以防止文件缓存,每次都会生成4位的hash串 filename: 'bundle.js', path: path.resolve }, plugins: [ // 通过new一下这个类来使用插件 new HtmlWebpackPlugin({ // 用哪个html作为模板 // 在src目录下创建一个index.html页面当做模板来用 template: './src/index.html', hash: true, // 会在打包好的bundle.js后面加上hash串 }) ]}
(22)提取公共代码

对于基本不会发生变化的代码提取出来,比如react,redux,react-router。但是他们合并到了bundle.js中了,每次项目发布需要重新提取。
CommonsChunkPlugin 可以用于将模块分离到单独的文件中。能够在每次修改后的构建结果中,将 webpack 的样板(boilerplate)和 manifest 提取出来。通过指定 entry 配置中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中:

通过上面的配置后,我们再npm run build打包看一下现在是个什么样子了

a.配置webpack.dev.config.js
+ const 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'
        })
]

把react等库生成打包到vendor.hash.js里面去。

但是你现在可能发现编译生成的文件main.[hash].js和vendor.[hash].js生成的hash一样的,这里是个问题,因为呀,你每次修改代码,都会导致vendor.[hash].js名字改变,那我们提取出来的意义也就没了。其实文档上写的很清楚,

output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].[hash].js', //这里应该用chunkhash替换hash
        chunkFilename: '[name].[chunkhash].js'
    }

图片 15image.png

b.存在问题

但是无奈,如果用chunkhash,会报错。和webpack-dev-server --hot不兼容,具体看这里。
现在我们在配置开发版配置文件,就向webpack-dev-server妥协,因为我们要用他。问题先放这里,等会再配置正式版webpack.dev.config.js的时候要解决这个问题。

多页面开发,怎么配置多页面如果开发的时候不只一个页面,我们需要配置多页面,那么需要怎么来搞呢?不用担心,html-webpack-plugin插件自有办法,我们来观望一下

(23)构建生产环境

开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

let path = require;let HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = { // 多页面开发,怎么配置多页面 entry: { index: './src/index.js', login: './src/login.js' }, // 出口文件 output: { filename: '[name].js', path: path.resolve }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', filename: 'index.html', chunks: ['index'] // 对应关系,index.js对应的是index.html }), new HtmlWebpackPlugin({ template: './src/index2.html', filename: 'login.html', chunks: ['login'] // 对应关系,login.js对应的是login.html }) ]}
a. 编写webpack.config.js

在webpack.dev.config.js基础上做一下修改:
①先删除webpack-dev-server相关的东西
②devtool的值改成cheap-module-source-map
③将filename: '[name].[hash].js',改成[chunkhash],这是因为npm run build后,发现app.xxx.js和vendor.xxx.js不一样了哦。

图片 16image.png

c. package.json增加打包脚本
"build":"webpack --config webpack.config.js"

运行 npm run build
注意,不是npm build

图片 17

image.png

生成了vundor.[chunkhash].html

上面基本介绍完了html和js的打包配置了,webpack对css的解析需要用到loader,所以我们先提前安装好,待会好方便使用

(24)压缩文件

需要下载一些解析css样式的loader

a. 安装

npm i --save-dev uglifyjs-webpack-plugin

npm i style-loader css-loader -D// 引入less文件的话,也需要安装对应的loadernpm i less less-loader -Dnpm i node-sass sass-loader -D
b. 配置

在webpack.config.js下

const UglifyJSPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  plugins: [
    new UglifyJSPlugin()
  ]
}

图片 18

image.png

此时,vendor.[hash].js小了很多

下面我们来看一下如何配置css文件的解析

(25)指定环境

许多 library 将通过与 process.env.NODE_ENV 环境变量关联,以决定 library 中应该引用哪些内容。例如,当不处于生产环境中时,某些 library 为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用 process.env.NODE_ENV === 'production' 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用 webpack 内置的 DefinePlugin 为所有的依赖定义这个变量:

在webpack.config.js下

module.exports = {
  plugins: [
       new webpack.DefinePlugin({
          'process.env': {
              'NODE_ENV': JSON.stringify('production')
           }
       })
  ]
}

图片 19

image.png

此时, vendor.[hash].js更小了

// index.jsimport './css/style.css'; // 引入cssimport './less/style.less'; // 引入lessconsole.log('这里是打包文件入口-index.js');// webpack.config.jsmodule.exports = { entry: { index: './src/index.js' }, output: { filename: 'bundle.js', path: path.resolve }, module: { rules: [ { test: /.css$/, // 解析css use: ['style-loader', 'css-loader'] // 从右向左解析 /* 也可以这样写,这种方式方便写一些配置参数 use: [ {loader: 'style-loader'}, {loader: 'css-loader'} ] */ } ] }}
(26)优化缓存

我们现在来解决(21).b存在的问题,如何让在修改任意一个.js文件后,vendor.[].js名字保持不变。Webpack官网推荐了HashedModuleIdsPlugin插件,见https://doc.webpack-china.org/plugins/hashed-module-ids-plugin

该插件会根据模块的相对路径生成一个四位数的hash作为模块id, 建议用于生产环境。

plugins: [

…

 new webpack.HashedModuleIdsPlugin()

 ]

然而,再次大包后vendor的名字还会发生变化。这时还需要添加一个runtime代码抽取

new webpack.optimize.CommonsChunkPlugin({

 name: 'runtime'

})

这时因为:

runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。runtime 包含:在模块交互时,连接模块所需的加载和解析逻辑。包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。

引入顺序在这里很重要。CommonsChunkPlugin 的 'vendor' 实例,必须在 'runtime' 实例之前引入

  • 此时打包后的css文件是以行内样式style的标签写进打包后的html页面中,如果样式很多的话,我们更希望直接用link的方式引入进去,这时候需要把css拆分出来
  • extract-text-webpack-plugin插件它的功效就在于会将打包到js里的css文件进行一个拆分,单独提取css
(27)打包优化

每次打包后的文件都存在了/dist文件夹下,我们希望每次打包前自动清理下dist文件。可以使用clean-webpack-plugin进行清理。

// @next表示可以支持webpack4版本的插件npm i extract-text-webpack-plugin@next -D

let path = require;let HtmlWebpackPlugin = require('html-webpack-plugin');// 拆分css样式的插件let ExtractTextWebpackPlugin = require('extract-text-webpack-plugin');module: { rules: [ { test: /.less$/, // 解析less use: ExtractTextWebpackPlugin.extract({ // 将css用link的方式引入就不再需要style-loader了 fallback: "style-loader", use: ['css-loader', 'less-loader'] // 从右向左解析 }) }, { test: /.scss$/, // 解析scss use: ExtractTextWebpackPlugin.extract({ // 将css用link的方式引入就不再需要style-loader了 fallback: "style-loader", use: ['css-loader', 'sass-loader'] // 从右向左解析 }) }, { test: /.css$/, // 解析css use: ExtractTextWebpackPlugin.extract({ // 将css用link的方式引入就不再需要style-loader了 fallback: "style-loader", use: ['css-loader'] }) } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', }), // 拆分后会把css文件放到dist目录下的css/style.css new ExtractTextWebpackPlugin('css/style.css') ]
a. 安装

npm install clean-webpack-plugin --save-dev

图片 20image.png

b.配置

webpack.config.js下

+ const CleanWebpackPlugin = require('clean-webpack-plugin');

+plugins: [

…

 new CleanWebpackPlugin(['dist'])

]

运行nom run build会发现,之前的./dist文件先被删除后才生成新的文件。

另一个插件mini-css-extract-plugin也是可以办到的,它可以说是为webpack4而生的,在这里就简单的提一下

(28)抽取CSS

目前的CSS打包进了js里面,webpack提供了一个插件,可以将css提取出来。Webpack提供了extract-text-webpack-plugin插件,可以提取单独的css文件。见:https://github.com/webpack-contrib/extract-text-webpack-plugin。

npm i mini-css-extract-plugin -D

let MiniCssExtractPlugin = require('mini-css-extract-plugin');module.exports = { module: { rules: [ { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] } ] }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/a.css' // 指定打包后的css }) ]}
a. 安装

npm install --save-dev extract-text-webpack-plugin

这里要着重说一下上面两个插件的区别了,个人还是建议用extract-text-webpack-plugin的,毕竟从之前的版本承接下来的,虽然在安包的时候需要@next,但是还是值得信赖的

b. 配置

webpack.config.js

const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {

 module: {

 rules: [

 {

 test: /.scss$/,

 use: ExtractTextPlugin.extract({

 fallback: 'style-loader',

 use: ['css-loader', 'sass-loader']

 })

 }

 ]

 },

 plugins: [

…

new ExtractTextPlugin({

 filename: '[name].[contenthash:5].css',

 allChunks: true

 })

 ]

}

而且现在的extract-text-webpack-plugin也支持了拆分成多个css,而目前mini-css-extract-plugin还不支持此功能

// 正常写入的lesslet styleLess = new ExtractTextWebpackPlugin('css/style.css');// resetlet resetCss = new ExtractTextWebpackPlugin('css/reset.css');module.exports = { module: { rules: [ { test: /.css$/, use: resetCss.extract({ fallback: "style-loader", use: 'css-loader' }) }, { test: /.less$/, use: styleLess.extract({ fallback: "style-loader", use: ['css-loader', 'less-loader'] // 从右向左解析 }) } ] }, plugins: [ styleLess, resetCss ]}

通过这样操作后可以打包成两个不同的css文件,如下图

图片 21image.png

npm i file-loader url-loader -D

如果是在css文件里引入的如背景图之类的图片,就需要指定一下相对路径

module.exports = { module: { rules: [ { test: /.(jpe?g|png|gif)$/, use: [ { loader: 'url-loader', options: { limit: 8192, // 小于8k的图片自动转成base64格式,并且不会存在实体图片 outputPath: 'images/' // 图片打包后存放的目录 } } ] } ] }}

在css中指定了publicPath路径这样就可以根据相对路径引用到图片资源了,如下图所示

图片 22image.png

页面中经常会用到img标签,img引用的图片地址也需要一个loader来帮我们处理好

npm i html-withimg-loader -D

 module.exports = { module: { rules: [ { test: /.$/, use: 'html-withimg-loader' } ] }}

这样再打包后的html文件下img就可以正常引用图片路径了

图片 23image.png

字体图标和svg图片都可以通过file-loader来解析

module.exports = { module: { rules: [ { test: /.(eot|ttf|woff|svg)$/, use: 'file-loader' } ] }}

这样即使样式中引入了这类格式的图标或者图片都没有问题了,img如果也引用svg格式的话,配合上面写好的html-withimg-loader就都没有问题了

通过postcss中的autoprefixer可以实现将CSS3中的一些需要兼容写法的属性添加响应的前缀,这样省去我们不少的时间

由于也是一个loader加载器,我们也需要先安装一下

npm i postcss-loader autoprefixer -D

安装后,我们还需要像webpack一样写一个config的配置文件,在项目根目录下创建一个postcss.config.js文件,配置如下:

module.exports = { plugins: [ require('autoprefixer')({ "browsers": [ "defaults", "not ie < 11", "last 2 versions", "> 1%", "iOS 7", "last 3 iOS versions" ] }) ]};

然后在webpack里配置postcss-loader

module.exports = { module: { rules: [ { test: /.less$/, // 解析less use: ExtractTextWebpackPlugin.extract({ // 将css用link的方式引入就不再需要style-loader了 fallback: "style-loader", use: ['css-loader', 'postcss-loader', 'less-loader'] // 从右向左解析 }) }, { test: /.scss$/, // 解析scss use: ExtractTextWebpackPlugin.extract({ // 将css用link的方式引入就不再需要style-loader了 fallback: "style-loader", use: ['css-loader', 'postcss-loader', 'sass-loader'] // 从右向左解析 }) }, { test: /.css$/, // 解析css use: ExtractTextWebpackPlugin.extract({ // 将css用link的方式引入就不再需要style-loader了 fallback: "style-loader", use: ['css-loader', 'postcss-loader'] }) }, ] }}

在实际开发中,我们在大量的使用着ES6及之后的api去写代码,这样会提高我们写代码的速度,不过由于低版本浏览器的存在,不得不需要转换成兼容的代码,于是就有了常用的Babel了

Babel会将ES6的代码转成ES5的代码

npm i babel-core babel-loader babel-preset-env babel-preset-stage-3 babel-preset-react babel-polyfill babel-plugin-import babel-loader babel-register -Dbabel-preset-stage-3 使用这个插件来编译为了后面使用...state扩展运算符可以使用

版权声明:本文由龙竞技官网发布于龙竞技官网,转载请注明出处:react+webpack+react-router+redux项目搭建(四)