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

(12)react-redux

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

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

a.安装react-redux

npm install --save react-redux

b.创建Counter组件

src/Counter/Counter.js中

import React, {Component} from 'react';

export default class Counter extends Component {
    render() {
        return (
            
当前计数为(显示redux计数)
) } }

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

+ import Counter from 'pages/Counter/Counter';
+ 
  • Counter
  • +

    npm start查看效果

    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(
           
               
                   {RootElement}
               
           ,
           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 (
                
    当前计数为{this.props.counter.count}
    ) } } 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


    react+webpack+react-router+redux项目搭建(四)_第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的属性。也可以将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 替代该方法。

    (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,显示错误信息。

    a.创建后台API

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

    
    {
    
     "name": "LALA",
    
     "intro": "enjoy hotpot"
    
    }
    
    
    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。

    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;
    
     }
    
    }
    
    
    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)
    
     }
    
    }
    
    
    e. redux-thunk

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

    f. 安装redux-thunk

    npm install --save redux-thunk

    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;
    
    
    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(
    
     
    { isLoading ? '请求信息中.......' : ( errorMsg ? errorMsg :

    用户信息:

    用户名:{userInfo.name}

    介绍:{userInfo.intro}

    ) }
    ) } } export default connect((state) => ({userInfo: state.userInfo}), {getUserInfo})(UserInfo);
    i. 添加路由/userInfo

    在 src/router/router.js 文件下

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

    npm start,查看效果
    react+webpack+react-router+redux项目搭建(四)_第2张图片
    3.png
    d. 修改webpack的output属性
        output: {
        path: path.join(__dirname, 'dist'),
        filename: 'app.js',
         chunkFilename: '[name].js'
      }
    
    react+webpack+react-router+redux项目搭建(四)_第3张图片
    4.png

    此时的文件名由:

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

    中的name值决定

    (20)webpack缓存

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

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

    打包后的文件:


    react+webpack+react-router+redux项目搭建(四)_第4张图片
    6.png

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

    (21)HtmlWebpackPlugin

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

    a. 安装

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

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

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

    
    
    
        
        Data
    
    
    

    此时我们会发现


    image.png

    找到


    react+webpack+react-router+redux项目搭建(四)_第5张图片

    我们发现之前在dist/index.html中的

    你可能感兴趣的:(react+webpack+react-router+redux项目搭建(四))