使用React構建精簡版本掘金(五)

距離上篇文章已經過去了大半個月,本來打算只更新代碼,最後還是決定把做的過程中遇到的問題記錄出來,說不定就可以幫助一些同學,也算是幸事,如果沒有,那就當作自己梳理知識點吧!

該篇文章主要講述以下知識點:

  • 如何mock數據
  • React組件中修改Redux中的數據

mock數據

有三種方式:

  • 利用node搭服務,mock數據
  • 利用現成網上mock服務,比如easy mock
  • 本地json數據模擬請求

介紹下本地數據模擬請求,利用axios請求數據:

import axios from 'axios';

const BaseUrl='./mock/homeData/';
const getHome=()=>{
    return axios.get(BaseUrl+'home.json').then(res=>{
        return res.data
    })
}

const getArticleList=()=>{
    return axios.get(BaseUrl+'articleList.json').then(res=>{
        return res.data
    })
}

export { getHome,getArticleList }

本地json數據存放位置如下

React組件中修改Redux中的數據

定義好state,reducer

//userReducer.js

// 1.定義默認數據
let initialState = {
    userId:'',
    userName:'',//實際項目與此不同
    userImage: '',
    userDesc:''
}

// action creators
export const actions = {
    login: (userInfo) => {
        return {type: 'CHANGE_USER',userName: userInfo.username,userId:userInfo.userId}
    },
    logout:()=>{
        return {type:'LOGOUT'}
    }
};

// 2.Reducer
const userReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'CHANGE_USERIMAGE':
            return { ...state, userImage: action.userImage }
        case 'CHANGE_USERID':
            return { ...state,userId:action.userId}
        case 'CHANGE_USER':
            return { ...state,userName:action.userName,userId:action.userId}
        case 'LOGOUT':
            return {...state,userName:'',userId:''}
        default:
            return state
    }
}
// 3.導出
export default userReducer;

在組件中操作修改state

...
this.props.login({username:username,userId:userId});//修改存儲在redux中的用戶信息
...
const mapStateToProps = (state, ownProps) => {
  return {
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return bindActionCreators({
    login: authActions.login
  },dispatch);
}

Login=connect(mapStateToProps,mapDispatchToProps)(Login)

export default Login;

Redux通過reducer解析action。reducer是一個普通的js函數,接受action作爲參數,然後返回一個新的state。

三大原則

  • 唯一數據源
  • 保持state狀態只讀
  • state的改變必須通過純函數完成

Redux

Redux應用只維護一個全局的狀態對象,存在Redux的store中。程序任何時候都不能直接去修改狀態state,如果需要修改state,必須發送一個action,通過action去描述如何修改state。action描述了改變state的意圖,真正對state作出修改的是reducer。reducer必須是純函數,所以reducer在收到action後,不能直接去修改原來的狀態state,而是應該創建一個新的狀態對象返回。

純函數需要滿足兩個條件:
(1)對於同樣的參數值,函數返回結果總是相同的,就是函數結果不依賴任何在程序執行過程中可能改變的變量。
(2)函數的執行過程不會產生比的副作用,例如會修改外部對象,有時會去修改頁面標題,這就是副作用。

數據流向的一個簡單示意圖,來自Flux官網

可以看出Redux應用的主要組成有action,reducer和store。

action

action是Redux中的信息載體,是store的唯一來源。把action發給store必須通過store中的dispach方法。其實action就是一個普通的js對象,但是每個action必須有一個type屬性用來描述action類型,要做什麼操作。除了type屬性歪,action的結構完全由你決定,不過應該保證可以清晰描述使用場景。

function logout(info){
    return {
        type:'LOGOUT',
        info
    }
}

reducer

reducer根據action做出響應,決定如何修改應用狀態state。其實在寫reducer之前就應該設計好state。

state:

// 1.定義默認數據
let initialState = {
    userId:'',
    userName:'',//實際項目與此不同
    userImage: '',
    userDesc:''
}

reducer:

// 2.Reducer
const userReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'CHANGE_USERIMAGE':
            return { ...state, userImage: action.userImage }
        case 'CHANGE_USERID':
            return { ...state,userId:action.userId}
        case 'CHANGE_USER':
            return { ...state,userName:action.userName,userId:action.userId}
        case 'LOGOUT':
            return {...state,userName:'',userId:''}
        default:
            return state
    }
}
這裏使用了ES6的擴展運算符(...)創建新的state對象,避免直接修改之前的state對象。

在實際項目中,可能會有很多reducer,就需要把reducer拆分保存在單獨的文件中。Redux提供了一個combineReducers函數,用來合併reducer。

import {combineReducers} from 'redux';

import pageHeaderReducer from './pageHeader.js';
import userReducer from './userReducer';

const appReducer = combineReducers({
    pageHeaderReducer,
    userReducer,
});
export default appReducer;

store

store是Redux中的一個對象,作爲action和reducer之間的一個橋樑。
作用:

  • 保存應用狀態
  • 通過方法獲取應用狀態
  • 通過dispatch(action)發送更新狀態的指令
  • 通過方法subscribe(listener)註冊監聽函數、監聽狀態的改變。

總結下Redux中的數據流過程:

  • (1)通過調用store.dispatch(action)。一個action描述了“發生了什麼”的對象以及可能會攜帶一些參數。store.dispatch(action)可以在應用中任何位置調用。
  • (2)Redux的store調用reducer函數。store傳遞兩個參數給reducer,分別是當前應用的狀態和action,reducer必須是純函數。
  • (3)根reducer可以把多個子reducer合併在一起,返回組成最終的應用狀態。利用combineReducers進行組合。
  • (4)Redux的store保存根reducer返回的完整應用狀態,這整個流程走完,應用狀態才完成更新。

在React中使用Redux

基礎安裝使用方法已經在第一篇中做了介紹,此處重點介紹下connect中的mapStateToProps和mapDispatchToProps。

mapStateToProps

mapStateToProps是一個函數,從名字上看,該函數作用就是把state轉換成props。state就是Redux store中保存的應用狀態,會作爲參數傳遞給mapStateToProps,props就是被連接展示組件的props。

const mapStateToProps = (state) => {
    return {
        userName:state.userReducer.userName,
        userImage:state.userReducer.userImage,
        userId:state.userReducer.userId
    }
}

當store中的state改變後,mapStateToProps就會重新執行,重新計算傳遞給展示組件的props,從而觸發組件的重新渲染。

注意:store中的state改變一定會導致mapStateToProps重新執行,但卻不一定會觸發組件渲染render方法重新執行。如果mapStateToProps新返回的對象和之前的對象淺比較相等,組件的shouldComponentUpdate方法就會返回false,組件的render方法就不會被再次觸發,這也是一個重要優化吧!

mapDispatchToProps

mapDispatchToProps接收store.dispatch方法作爲參數,返回展示組件用來修改state的函數。

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
      getOrder: (data) => dispatch(actionCreator(data))
  }
}

另外一種寫法,利用bindActionCreators

import { bindActionCreators } from "redux";

const mapDispatchToProps = (dispatch, ownProps) => {
  return bindActionCreators({
    getOrder: actionCreator.getOrder
  },dispatch);
}

bindActionCreators作用是將單個或多個ActionCreator轉化爲dispatch(action)的函數集合形式。個人感覺會內部自動注入dispatch,不用我們手動去dispatch。之後通過connect連接即可

const mapStateToProps = (state, ownProps) => {
  return {

  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return bindActionCreators({
    login: authActions.login
  },dispatch);
}

Login=connect(mapStateToProps,mapDispatchToProps)(Login)
注意connect函數參數必須第一個爲mapStateToProps,第二個必須爲mapDispatchToProps,可以不寫mapDispatchToProps,但是如果需要mapDispatchToProps卻不能不寫mapStateToProps,否則會報錯,個人第一次使用就犯了這個錯誤😓

囉哩囉嗦的說了這麼多,碼字不易,點贊再走哈。完整項目代碼在github,歡迎點個star,不勝感激👏👏👏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章