據說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推薦組件加上鍊接倉庫的過程了,下面再看兩張圖來給你們說明一下

上圖是剛進入頁面的時候,初始化階段

下圖爲切換分類的操做對應倉庫裏數據上的變化

好了,好了,寫了這麼多,你們看的也累了,感謝你們耐心的觀看了。

對於學習,咱們都要永不止步,感謝你們了,再見

相關文章
相關標籤/搜索