React基础(下)

使用Redux框架

Redux的流程:

[外链图片转存失败(img-ArLatakd-1562849830397)(./images/redux.png)]

1.store通过reducer创建了初始状态

2.view通过store.getState()获取到了store中保存的state挂载在了自己的状态上

3.用户产生了操作,调用了actions 的方法

4.actions的方法被调用,创建了带有标示性信息的action

5.actions将action通过调用store.dispatch方法发送到了reducer中

6.reducer接收到action并根据标识信息判断之后返回了新的state

7.store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state

Reducer必须是一个纯函数:

Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。Reducer不是只有Redux里才有,之前学的数组方法reduce, 它的第一个参数就是一个reducer

纯函数是函数式编程的概念,必须遵守以下一些约束。

  • 不得改写参数

  • 不能调用系统 I/O 的API

  • 不能调用Date.now()或者Math.random()等不纯的方法,因为每次会得到不一样的结果

由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,请参考下面的写法。

// State 是一个对象
function reducer(state = defaultState, action) {
  return Object.assign({}, state, { thingToChange });
  // 或者
  return { ...state, ...newState };
}

// State 是一个数组
function reducer(state = defaultState, action) {
  return [...state, newItem];
}

最好把 State 对象设成只读。要得到新的 State,唯一办法就是生成一个新对象。这样的好处是,任何时候,与某个 View 对应的 State 总是一个不变(immutable)的对象。

我们可以通过在createStore中传入第二个参数来设置默认的state,但是这种形式只适合于只有一个reducer的时候。

划分reducer:

因为一个应用中只能有一个大的state,这样的话reducer中的代码将会特别特别的多,那么就可以使用combineReducers方法将已经分开的reducer合并到一起

注意:

  1. 分离reducer的时候,每一个reducer维护的状态都应该不同
  2. 通过store.getState获取到的数据也是会按照reducers去划分的
  3. 划分多个reducer的时候,默认状态只能创建在reducer中,因为划分reducer的目的,就是为了让每一个reducer都去独立管理一部分状态

最开始一般基于计数器的例子讲解redux的基本使用即可

关于action/reducer/store的更多概念,请查看官网

Redux异步

通常情况下,action只是一个对象,不能包含异步操作,这导致了很多创建action的逻辑只能写在组件中,代码量较多也不便于复用,同时对该部分代码测试的时候也比较困难,组件的业务逻辑也不清晰,使用中间件了之后,可以通过actionCreator异步编写action,这样代码就会拆分到actionCreator中,可维护性大大提高,可以方便于测试、复用,同时actionCreator还集成了异步操作中不同的action派发机制,减少编码过程中的代码量

常见的异步库:

  • Redux-thunk(就讲这个)
  • Redux-saga
  • Redux-effects
  • Redux-side-effects
  • Redux-loop
  • Redux-observable

基于Promise的异步库:

  • Redux-promise
  • Redux-promises
  • Redux-simple-promise
  • Redux-promise-middleware

容器组件(Smart/Container Components)和展示组件(Dumb/Presentational Components)

展示组件容器组件
作用描述如何展现(骨架、样式)描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源props监听 Redux state
数据修改从 props 调用回调函数向 Redux 派发 actions
调用方式手动通常由 React Redux 生成

使用react-redux

可以先结合context来手动连接react和redux。

react-redux提供两个核心的api:

  • Provider: 提供store
  • connect: 用于连接容器组件和展示组件
  1. Provider

    根据单一store原则 ,一般只会出现在整个应用程序的最顶层。

  2. connect

    语法格式为

    connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)

    一般来说只会用到前面两个,它的作用是:

    • store.getState()的状态转化为展示组件的props
    • actionCreators转化为展示组件props上的方法

特别强调:

官网上的第二个参数为mapDispatchToProps, 实际上就是actionCreators

只要上层中有Provider组件并且提供了store, 那么,子孙级别的任何组件,要想使用store里的状态,都可以通过connect方法进行连接。如果只是想连接actionCreators,可以第一个参数传递为null

Mobx

Mobx是一个功能强大,上手非常容易的状态管理工具。redux的作者也曾经向大家推荐过它,在不少情况下可以使用Mobx来替代掉redux。

[外链图片转存失败(img-5pzyoWxB-1562849830402)(E:/1902/03-React.js/note/images/mobx-flow.png)]

这张图来自于官网,把这张图理解清楚了。基本上对于mobx的理解就算入门了。

官网有明确的核心概念使用方法,并配有egghead的视频教程。这里就不一一赘述了。

要特别注意当使用 mobx-react 时可以定义一个新的生命周期钩子函数 componentWillReact。当组件因为它观察的数据发生了改变,它会安排重新渲染,这个时候 componentWillReact 会被触发。这使得它很容易追溯渲染并找到导致渲染的操作(action)。

  • componentWillReact 不接收参数
  • componentWillReact 初始化渲染前不会触发 (使用 componentWillMount 替代)
  • componentWillReact 对于 mobx-react@4+, 当接收新的 props 时并在 setState 调用后会触发此钩子
  • 要触发componentWillReact必须在render里面用到被观察的变量
  • 使用Mobx之后不会触发componentWillReceiveProps

react脚手架 - Mobx配置 ( 装饰器 )

  1. 创建项目
    create-react-app app

  2. 进入项目
    cd app

  3. 进行配置文件抽离
    yarn eject

  4. 安装mobx mobx-react

    mobx 是状态管理工具

    mobx-react 是做数据分片和数据获取

    $ $yarn add mobx mobx-react
    注意: 如果git冲突
    解决: 我们要原文件先放到本地暂存盘
    git add .
    git commit -m ’

    ​ 然后 : 安装mobx mobx-react’
    ​ 注意不要git push

  5. 配置装饰器( 修饰器 es6 ) babel
    yarn add babel-plugin-transform-decorators-legacy -D
    yarn add @babel/preset-env -D
    yarn add babel-plugin-transform-class-properties -D
    yarn add @babel/plugin-proposal-decorators -D

  6. 配置package.json
    “babel”: {
    “plugins”: [
    [
    “@babel/plugin-proposal-decorators”,
    {
    “legacy”: true
    }
    ],
    “transform-class-properties”
    ],
    “presets”: [
    “react-app”,
    “@babel/preset-env”
    ]
    },

注意: 以下两个配置顺序不可更改
[
“@babel/plugin-proposal-decorators”,
{
“legacy”: true
}
],
“transform-class-properties”

项目中 mobx应该怎么用?

  1. 新建store目录
    src
    store
    home
    index.js
    car
    index.js
    index.js

  2. 在入口文件中 使用 Provider

    import store from ‘./store’
    import { Provider } from ‘mobx-react’

    ReactDOM.render(



    , document.getElementById(‘root’));

  3. 哪个组件使用 , 就在哪个组件中 “注入” inject

    import {inject} from ‘mobx-react’

    @inject(‘store’)

  4. 打造mobx 数据包

    import {
    observable, computed,action
    } from ‘mobx’
    class Home {
    @observable //监听 age
    age = 18

    @computed //当age发生改变时, 自动触发
    get doubleAge(){
    return this.age *= 2
    }

    @action // 用户操作 事件调用
    increment(){
    this.props.store.home.age ++
    console.log( this.props.store.home.age )

       //数据请求
       fetch('/data/data.json')
       .then(res => res.json())
       .then( result => console.log( result ))
       .catch( error => console.log( error ))
     }

 }

 const home = new Home()

 export default home
  1. 打造store
    store/index.js

    import home from ‘./home’
    const store = {
    //实例
    home
    }

    export default store

  2. 组件内使用数据

    this.props.store.xxx 可以拿到数据

    注意:

    1. this.porps里面没有找到 @action 装饰器定义的方法, 但是可以直接使用,

    2. this会丢失

      this.props.store.home.increment.bind(this)

React Router

React Router现在的版本是5, 于2019年3月21日搞笑的发布,React Router的官网链接, 本来是要发布4.4的版本的,结果成了5。从4开始,使用方式相对于之前版本的思想有所不同。之前版本的思想是传统的思想:路由应该统一在一处渲染, Router 4之后是这样的思想:一切皆组件

React Router包含了四个包:

包名Description
react-routerReact Router核心api
react-router-domReact Router的DOM绑定,在浏览器中运行不需要额外安装react-router
react-router-nativeReact Native 中使用,而实际的应用中,其实不会使用这个。
react-router-config静态路由的配置

主要使用react-router-dom

使用方式

正常情况下,直接按照官网的demo就理解 路由的使用方式,有几个点需要特别的强调:

  • Route组件的exact属性

exact属性标识是否为严格匹配, 为true是表示严格匹配,为false时为正常匹配。

  • Route组件的render属性而不是component属性

怎么在渲染组件的时候,对组件传递属性呢?使用component的方式是不能直接在组件上添加属性的。所以,React Router的Route组件提供了另一种渲染组件的方式 render, 这个常用于页面组件级别的权限管理。

  • 路由的参数传递与获取

  • Switch组件

总是渲染第一个匹配到的组件

  • 处理404与默认页

  • withRoute高阶组件的使用

  • 管理一个项目路由的方法

  • code spliting

  • HashRouter和BrowserRouter的区别,前端路由和后端路由的区别。这个在Vue里应该有讲过了。

React Router基本原理

React Router甚至大部分的前端路由都是依赖于history.js的,它是一个独立的第三方js库。可以用来兼容在不同浏览器、不同环境下对历史记录的管理,拥有统一的API。

  • 老浏览器的history: 通过hash来存储在不同状态下的history信息,对应createHashHistory,通过检测location.hash的值的变化,使用location.replace方法来实现url跳转。通过注册监听window对象上的hashChange事件来监听路由的变化,实现历史记录的回退。
  • 高版本浏览器: 利用HTML5里面的history,对应createBrowserHistory, 使用包括pushStatereplaceState方法来进行跳转。通过注册监听window对象上的popstate事件来监听路由的变化,实现历史记录的回退。
  • node环境下: 在内存中进行历史记录的存储,对应createMemoryHistory。直接在内存里pushpop状态。

Immutable.js

JavaScript数据修改的问题

看一段大家熟悉的代码

const state = {
  str: '我的世界',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = state

console.log(newState === state) // true

由于js的对象和数组都是引用类型。所以newState的state实际上是指向于同一块内存地址的, 所以结果是newState和state是相等的。

尝试修改一下数据

const state = {
  str: '我的世界',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = state

newState.str = '我的世界梦想'

console.log(state.str, newState.str)

可以看到,newState的修改也会引起state的修改。要解决这个问题,js中提供了另一种修改数据的方式,要修改一个数据之前先制作一份数据的拷贝,像这样

const state = {
  str: '我的世界',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = Object.assign({}, state)

newState.str = '我的世界梦想'

console.log(state.str, newState.str)

我们可以使用很多方式在js中复制数据,比如, Object.assign, Object.freeze, slice, concat, map, filter, reduce等方式进行复制,但这些都是浅拷贝,就是只拷贝第一层数据,更深层的数据还是同一个引用,比如:

const state = {
  str: '我的世界',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = Object.assign({}, state)

newState.obj.y = 2
newState.arr.push(4)

console.log(state, newState)

可以看到,当在更改newState更深层次的数据的时候,还是会影响到state的值。如果要深层复制,就得一层一层的做递归拷贝,这是一个复杂的问题。虽然有些第三方的库已经帮我们做好了,比如lodashcloneDeep方法。深拷贝是非常消耗性能的。

import { cloneDeep } from 'lodash'

const state = {
  str: '我的世界',
  obj: {
    y: 1
  },
  arr: [1, 2, 3]
}
const newState = cloneDeep(state)

newState.obj.y = 2
newState.arr.push(4)

console.log(state, newState)

什么是不可变数据

不可变数据 (Immutable Data )就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是持久化数据结构( Persistent Data Structure),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的s性能损耗,Immutable 使用了 结构共享(Structural Sharing),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。

[外链图片转存失败(img-uIjj9g0C-1562849830408)(./images/structure-sharing.png)]

immutable.js的优缺点

优点:

  • 降低immutable带来的复杂度
  • 节省内存
  • 历史追溯性(时间旅行):时间旅行指的是,每时每刻的值都被保留了,想回退到哪一步只要简单的将数据取出就行,想一下如果现在页面有个撤销的操作,撤销前的数据被保留了,只需要取出就行,这个特性在redux或者flux中特别有用
  • 拥抱函数式编程:immutable本来就是函数式编程的概念,纯函数式编程的特点就是,只要输入一致,输出必然一致,相比于面向对象,这样开发组件和调试更方便。推荐一本函数式编程的在线免费书《JS 函数式编程指南》, 此书可以推荐给学生做为课外补充阅读。

缺点:

  • 需要重新学习api
  • 资源包大小增加(源码5000行左右)
  • 容易与原生对象混淆:由于api与原生不同,混用的话容易出错。

使用Immutable.js

参考官网重点讲解数据不可变数据的创建、更新及比较方式 。对于就业班来说,掌握以下知识点即可。

Map

import { Map } from 'immutable'

const map = Map({
  a: 1,
  b: 2,
  c: 3
})

const newMap = map.set('b', 20) // immutable数据每次都是生成新的再重新调用set进行修改,所以需要 重新赋值给一个新的变量

console.log(map, newMap) // immutable.Map不是原生的对象
console.log(map.b, newMap.b) // immutable.Map不是原生的对象, 所以是undefined
console.log(map.get('b'), newMap.get('b')) // 要取值,需要调用get(key)方法,可以看到,两个值不一样

const obj = {
  a: 1,
  b: 2,
  c: 3
}

console.log(Map.isMap(map), Map.isMap(obj)) // true false, 使用Map.isMap来判断是否是一个immutable.Map类型

List

import { List } from 'immutable'

const list = List([1, 2, 3, 4])
const newList = list.push(5)
console.log(list, newList)
console.log(list[4], newList[4]) // undefined undefined
console.log(list.get(4), newList.get(4)) // undefined 5
console.log(list.size, newList.size) // 4 5

const arr = [1, 2, 3, 4]

console.log(List.isList(list), List.isList(arr)) // true false

equals & is

import { Map, is } from 'immutable'

const map = Map({
  a: 1,
  b: 2,
  c: 3
})

const anotherMap = Map({
  a: 1,
  b: 2,
  c: 3
})

console.log(map == anotherMap) // false
console.log(map === anotherMap) // false
console.log(map.equals(anotherMap)) // 使用equals进行比较 true
console.log(is(map, anotherMap)) // 使用is进行比较 true

List常用api

import { List } from 'immutable'

const list = List([1, 2, 3, 4])
const list1 = list.push(5)
const list2 = list1.unshift(0)
const list3 = list.concat(list1, list2)
const list4 = list.map(v => v * 2)

console.log(list.size, list1.size, list2.size, list3.size, list4.toJS()) // 4 5 6 15, [2, 4, 6, 8]

Map常用api

import { Map } from 'immutable'

const alpha = Map({
  a: 1,
  b: 2,
  c: 3
})
const objKeys = alpha.map((v, k) => k)
console.log(objKeys.join()) // a, b, c


const map1 = Map({
  a: 1,
  b: 2
})
const map2 = Map({
  c: 3,
  d: 4
})
const obj = {
  d: 400,
  e: 50
}

const mergedMap = map1.merge(map2, obj)

console.log(mergedMap.toObject())
console.log(mergedMap.toJS())

嵌套数据结构

const { fromJS } = require('immutable');
const nested = fromJS({ a: { b: { c: [ 3, 4, 5 ] } } });

const nested2 = nested.mergeDeep({ a: { b: { d: 6 } } });
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 6 } } }

console.log(nested2.getIn([ 'a', 'b', 'd' ])); // 6

const nested3 = nested2.updateIn([ 'a', 'b', 'd' ], value => value + 1);
console.log(nested3);
// Map { a: Map { b: Map { c: List [ 3, 4, 5 ], d: 7 } } }

const nested4 = nested3.updateIn([ 'a', 'b', 'c' ], list => list.push(6));
// Map { a: Map { b: Map { c: List [ 3, 4, 5, 6 ], d: 7 } } }

在redux中使用immutable.js

redux官网推荐使用redux-immutable进行redux和immutable的集成。几个注意点:

redux中,利用combineReducers来合并多个reduce, redux自带的combineReducers只支持原生js形式的,所以需要使用redux-immutable提供的combineReducers来代替

// 使用redux-immutable提供的combineReducers方法替换redux里的combineReducers
import {combineReducers} from 'redux-immutable'
import reducerOne from './reducerOne'
import reducerTwo from './reducerTwo'
 
const rootReducer = combineReducers({
    reducerOne,
    reducerTwo
});
 
export default rootReducer;

reducer中的initialState也需要初始化成immutable类型, 比如一个counter的reducer

import { Map } from 'immutable'

import ActionTypes from '../actions'

const initialState = Map({
  count: 0
})

export default (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.INCREAMENT:
      return state.set('count', state.get('count') + 1) // 使用set或setIn来更改值, get或者getIn来取值
    case ActionTypes.DECREAMENT:
      return state.set('count', state.get('count') - 1)
    default:
      return state
  }
}

state成为了immutable类型,connectmapStateToProp也需要相应的改变

const mapStateToProps = state => ({
  count: state.getIn(['counter', 'count']) // 永远不要在mapStateToProps里使用`toJS`方法,因为它永远返回一个新的对象
})

shouldComponentUpdate里就可以使用immutable.is或者instance.equals来进行数据的对比了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值