据说redux和react-redux在写项目中更配哦

最近,因为接触了React的项目,因此开始慢慢去学习关于React全家桶的一些内容,其中有一块内容用到的较为频繁,因而也开始恶补这方面的知识vue

如标题所示,这篇文章就是关于redux & react-redux在实际工做中是如何使用的react

那么,闲言少叙,仍是从头开始讲起吧vuex

它们是谁?

  • 我的认为它是一个专门用来建立仓库的东东,你能够叫它为store
  • 经过redux库里的createStore方法来建立仓库
  • 值得傲娇的是redux并不像vuex那样,必须依赖vue而使用,单独拿出来也是出类拔萃的

那么,问题来了?npm

  • react-redux又是作甚的?
    • 首先,从名字上来看,就应该能了解,这是结合react与redux一块儿来使用的
    • 其次,是重点,它是用来链接react组件store仓库的桥梁

OK,大体知道它们的做用了,那么直接开始搞起redux

无安装不建仓

// 安装 redux 和 react-redux
npm i redux react-redux --save
复制代码

再常规不过的结构

如何用之

咱们先以最简单的组件为例,看看组件仓库是如何经过react-redux创建链接的api

在components文件夹下建立一个Counter.js组件,就是为了作个加减的组件数组

// components/Counter.js文件

import React, { Component } from 'react';

export default class Counter extends Component {
    constructor() {
        super();    // 继承者们
        this.state = { number: 0 };
    }
    
    render() {
        return (<div>
            <button>-</button>
            {this.state.number}
            <button>+</button>
        </div>)
    }
}
复制代码

草草完事,就这样写完了Counter组件了,下面在index主入口文件里引入一下bash

// index.js入口文件

import React from 'react';
import { render } from 'react-dom';
import Counter from './components/Counter';

// 开始渲染了
// render方法第一个参数是要渲染的组件,第二个是目标节点
render(
    <Counter />, 
    document.getElementById('root')
);
复制代码

忘记说了,为了方便,我是用create-react-app脚手架建立的项目,因此先全局安装一下,而后再建立项目并启动该项目数据结构

npm i -g create-react-app

// 建立项目
create-react-app 项目名
// 启动
npm run start
复制代码

项目跑起来后看到的应该是这个样子的app

image

下面就针对这个Counter组件来亲身使用一下redux & react-redux在项目里的使用状况吧

使用redux和react-redux

store目录下的结构就如最开始看到的,下面我再分析一下里面的内容都分别是有何用处的

  • actions目录
    • 这里是放一些方法,就是在组件里方便调用的方法(更多的是请求数据的状况)
    • 你也能够把这里当成是存数据用的地方也OK
  • reducers目录
    • 这里就是用来取数据的地方了
  • action-types.js
    • 定义各类组件须要的类型(如: INIT_DATA,GET_DATA,CHANGE_TYPE啊)
  • index.js
    • 这里是真正用来建立仓库的地方

好了,那就赶忙写起来吧

The first, 先来定义action-types

// store/action-types.js

// Counter组件用到的types
export const ADD = 'ADD';
export const MINUS = 'MINUS';
复制代码

action-types里定义的都是根据各组件的须要才定义的类型常量,属于一一对应的一种关系

The second, 再写一下关于actions动做的(就是应该作什么事)

// store/actions/counter.js

// 也是有固定套路的

import * as types from '../action-types';

// 返回一个包含不一样类型的对象
export default {
    add(count) { // 加数字
        return {type: types.ADD, count};
    },
    minus(count) {  // 减数字
        return {type: types.MINUS, count};
    }
}
复制代码

因为Counter组件须要处理加减操做,因此在actions里的counter.js文件里来写下对应的执行方法来

Finnally,修改reducer并处理此动做

// store/reducers/counter.js

// 常规写法, reducer是个函数

// 引入你组件须要的type
import * as types from '../action-types';

// 初始化状态
const initState = { number: 0 };

function counter(state = initState, action) {
    
    switch(action.type) {
        case types.ADD:
            return { number: state.number + action.count };
        case types.MINUS:
            return { number: state.number - action.count };
        default:
            return state;
    }
    
    return state;
}

export default counter;
复制代码

reducer为何这样写?

  • initState状态是为了在组件加载的时候第一次仓库里并没有state,因此为了防止undefined的报错状况,先给一个初始化状态
  • initState虽然是个初始化状态,但这其实就是你组件所须要的数据结构,因此须要好好设计设计
  • action就是在actions目录里对应文件传递过来的状态,它长这个样子 {type: 'ADD', count}
  • counter里两个参数,state表明着过去的状态,action表明的是新的状态
  • 之因此叫作reducer也是借鉴了数组的reduce方法,里面的两个参数和如今有殊途同归之妙
  • 固然最重要的是,这个函数作的第一步就是把state状态返回出去

说最后有点为时过早了,咱们尚未建立仓库呢

并且在建立以前还要整合一下reducer,由于这才一个counter,真实项目里还会根据不一样的组件写出来不一样的reducer呢

因此为了避免冲突,咱们利用redux提供的combineReducers方法来合并它们

合并reducer

在reducers里建立一个index.js文件,用来合并reducer

// combineReducers方法就是专门用来合并不一样的reducer的
import { combineReducers } from 'redux';
// 引入关于Counter组件的reducer
import counter from './counter';
// 引入其余的reducer
import list from './list';

// 合并开始
export default combineReducers({
    counter,
    list      // 其余的reducer
});
复制代码

Let's Go 建仓吧

来到store目录下面的index.js中

// 引入redux提供的createStore方法来建立仓库
import { createStore } from 'redux';
// 引入全部用到的reducer
import reducer from './reducers';

const store = createStore(reducer);
export default store;
复制代码

准备起飞

好了,这样就把仓库建立完毕了,下面是最后的链接过程了,回到Counter组件里去修改一下

// components/Counter.js

import React, { Component } from 'react';
+++
// react-redux提供了connect方法,它是个高阶函数
import { connect } from 'react-redux';
import actions from '../store/actions/counter';
+++

// export default再也不直接默认导出Counter而是要写到下面,经过connect来实现高阶组件了(HOC)

class Counter extends Component {
    render() {
        // 经过mapStateToProps和mapDispatchToProps
        // 将number状态还有add和minus方法都转化到了props属性上了
        const { add, minus, number } = this.props;
        
        return (<div>
            <button onClick={() => minus(1)}>-</button>
            {number}
            <button onClick={() => add(2)}>+</button>
        </div>)
    }
};

+++
const mapStateToProps = state => {
    console.log(state);  // 长这样就是存的全部reducer:{counter: {number: 0}, list: {data: []}}
    
    return {
        number: state.counter.number
    };
};

const mapDispatchToProps = dispatch => {
    return {
        add: (n) => dispatch(actions.add(n)),
        minus: (n) => dispatch(actions.minus(n))
    };
};

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);
+++
复制代码

写完上面的代码,就实现了把Counter组件与store仓库链接起来的操做了。最后的最后,咱们就把index.js入口文件再修改一下,让全部组件均可以链接到store吧

奇迹即将发生

// index.js

import React from 'react';
import { render } from 'react-dom';
import Counter from './components/Counter';
+++
// Provider是个组件,有容乃大包容万物,不过只能有一个子元素
import { Provider } from 'react-redux';
import store from './store';
+++

// 开始渲染了
// render方法第一个参数是要渲染的组件,第二个是目标节点
render(
    <Provider store={stroe}>
        <React.Fragment>
            {/* 若是有多个组件,就必须用React.Fragment组件来包裹,它不会产生多余的标签元素,和vue的template相似 */}
            <Counter />
        </React.Fragment>
    </Provider>, 
    document.getElementById('root')
);
复制代码

一切都安静了,看看最终的效果

看到这里我就把项目中使用redux及react-redux的过程叙述完毕了,固然上面的栗子也是比较简单的demo了

实际项目会比这样的操做起来稍微麻烦些,不过大同小异,学会触类旁通都是能慢慢熟练运用起来的,下面再给你们看一下最近项目中我是如何书写的,写的很差仅供参考

只是为了让你们,慢慢领会,慢慢熟练,慢慢运用,慢慢越写越好

首先看一下效果图

此为连接地址,点击看看

下面我就跟你们说说我实现的思路(其实不难的,一块儿来看看吧)

付诸行动

看到上图所示,其实做为一个推荐列表来讲,无非就是两个状态要作管理

  1. 切换分类(全城热搜、景点、美食、酒店tab的切换)
  2. 获取列表数据(根据不一样分类获取对应数据)

先来写个action-types

在src目录下会建立一个store的文件夹,这里包含了actions,reducers,actions-types等须要的必备内容

// store/action-types.js

// 获取category列表
export const GET_RECOMMEND_LIST = 'GET_RECOMMEND_LIST';
// 切换category的type
export const CHANGE_CATEGORY = 'CHANGE_CATEGORY';
复制代码

OK,须要的动做类型写好了,那就继续写actions了

actions对应动做(方法)

// store/actions/recommend.js

// 引入全部的types常量
import * as types from '../action-types';
// 请求列表数据
import { getPoiList } from '../../api';

// 默认导出一个对象
export default {
    // 切换类型的方法()
    changeCategory(params, data) {
        let response;
        // 若是当前的数据已经存在仓库中就再也不发送请求
        if (!data[params.category]) {
            // 请求对应类型数据
            response = getPoiList({ params });
        }
        return {
            type: types.CHANGE_CATEGORY,
            category: params.category,
            key: params.keyword,
            response
        }
    },
    // 获取列表数据的方法
    getRecommendList({params}) {
        let data = getPoiList({ params });
        
        return {
            type: types.GET_RECOMMEND_LIST,
            response: data,
            params
        }
    }
}
复制代码

好了,写到这里就把actions里的方法都写完了,就这么两个而已

changeCategory不只仅作了切换tab的处理,还在切换的时候进行了请求上的优化

reducers里的recommend

// store/reducers/recommend.js

// 引入全部的types常量
import * as types from '../action-types';
// 初始化状态数据
const initState = {
    pathname: '',
    params: {},
    loading: false,
    data: {
        hot: null,
        food: null,
        hotel: null,
        scenic: null,
    },
    business: {},
    category: 'hot',
    key: '景点'
};

function recommend(state = initState, action) {
    switch(action.type) {
        case types.CHANGE_CATEGORY:
            // 从action里拿到传递过来的数据
            const { category, response, key, params } = action;
            
            let newState = Object.assign({}, state, {
                data: {
                    ...state.data,
                    [action['category']]: response
                }
            });
            
            return { ...state, ...newState };
        case types.GET_RECOMMEND_LIST:
            const { pathname, params, response = {}, loading = false } = action;
            let newState = Object.assign({}, state, {
                data: {
                    ...state.data,
                    [category]: response    // 将不一样类型的数据一一对应起来,如food:{response:[]}
                }
            });
            // 省略了一些处理广告数据的代码
            return newState;
        default:
            return state;
    }
    
    return state;
}

export default recommend;
复制代码

这样就写完了reducer了,其实在写多了以后,你们能稍微有点感悟了

其实上面的一顿操做,用三句话来讲就是

  1. 定义常量类型(actions-types)
  2. 存数据(actions)
  3. 取数据处理数据(reducers)

好了,最后的时刻到了,直接让咱们去看看在组件上是如何使用的吧

recommend组件链接仓库

在和store同级的目录中有一个components文件夹,这里放置一些经常使用的公共组件,recommend就是其中之一

下面就直接看看recommend是如何写的

// components/recommend/index.js

import React, { Component } from 'react';
import actions from '../../store/actions/recommend';
import { connect } from 'react-redux';
// 下面两个组件是用来下滑加载和loading的
import ScrollLoad from '../../common/ScrollLoad';
import Loading from '../../common/Loading';
// 列表数据渲染的组件
import List from './list';
import { CHANGE_CATEGORY, GET_RECOMMEND_LIST } from '../../store/action-types';

// 定义初始化的参数和tab列表数组方便渲染
const initParams = { keyword: '景点', category: 'hot' };
const navList = [
    { type: 'hot', name: '全城热搜', keyword: '景点' },
    { type: 'scenic', name: '景点', keyword: '风景名胜' },
    { type: 'food', name: '美食', keyword: '餐饮' },
    { type: 'hotel', name: '酒店', keyword: '酒店' }
];

class Recommend extends Component {
    // 在willMount生命周期的时候先请求列表数据
    componentWillMount() {
        const { getRecommendList } = this.props;
        // 初始化数据
        getRecommendList({ params: initParams });
    }
    
    // 处理点击切换tab并回传给父组件修改keyword请求不一样数据
    handleClick = (event) => {
        const { changeCategory, response } = this.props;
        const { data } = response;
        const category = event.target.dataset.category;
        const keyword = event.target.dataset.keyword;
        const obj = {
            category,
            keyword
        };
        // 修改对应类型
        changeCategory(obj, data);
    }
    // 滑动操做处理
    scrollHandle = ({ page, callback }) => {
        const { getRecommendList, response } = this.props;
        const { params, category, key, data } = response;
        let batch = data[category] && data[category].page + 1;

        const newParams = Object.assign({}, params, {
            batch
        });

        newParams.category = category;
        newParams.keyword = key;
        // 加载数据
        getRecommendList({ params: newParams }).then(() => {
            callback();
        });
    }
    
    render() {
        const { response } = this.props;
        const { params, data, category } = response;
        const categoryData = data[category];    // 分类数据
        let totalcount = categoryData && categoryData.totalcount;

        // 列表项
        const navItem = navList.map((item, i) =>
            <li className={category === item.type ? 'active' : ''}
                data-category={item.type}
                data-keyword={item.keyword}
                onClick={this.handleClick}
                key={i}>{item.name}
            </li>
        );

        return (
            <div className='recommend'>
                <ul className='recommend-nav'>{navItem}</ul>

                {totalcount ? <ScrollLoad totalCount={totalcount} scrollHandle={this.scrollHandle}>
                    <List response={response} />
                </ScrollLoad> : <Loading />}
            </div>
        );
    }
}

const mapStateToProps = state => {
    return {
        response: state.recommend
    }
};

const mapDispatchToProps = dispatch => {
    return {
        changeCategory: (params, data) => dispatch(actions.changeCategory(params, data)),
        getRecommendList: ({params}) => dispatch(actions.getRecommendList({params})
    }
};

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(RecommendList);
复制代码

以上就是大体完成了recommend推荐组件加上链接仓库的过程了,下面再看两张图来给你们说明一下

上图是刚进入页面的时候,初始化阶段

下图为切换分类的操做对应仓库里数据上的变化

好了,好了,写了这么多,你们看的也累了,感谢你们耐心的观看了。

对于学习,咱们都要永不止步,感谢你们了,再见

相关文章
相关标签/搜索