使用React构建精简版本掘金五

26次阅读

共计 5023 个字符,预计需要花费 13 分钟才能阅读完成。

距离上篇文章已经过去了大半个月,本来打算只更新代码,最后还是决定把做的过程中遇到的问题记录出来,说不定就可以帮助一些同学,也算是幸事,如果没有,那就当作自己梳理知识点吧!

该篇文章主要讲述以下知识点:

  • 如何 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,不胜感激????????????

正文完
 0