Redux、React和Redux配合、logger | 8月更文挑战

815 阅读7分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

Redux

Redux 是一个可预测状态容器。

官网:redux.js.org/

创建一个Redux项目用Nodejs调试

创建身份证

npm init

安装依赖

npm install --save redux

创建app.js

var redux = require("redux");

//创建一个函数,叫做reducer。
const reducer = (state = {"a" : 10} , action) => {
    if(action.type == "ADD"){
        return {
            "a" : state.a + 1
        }
    }else if(action.type == "MINUS"){
        return {
            "a" : state.a - 1
        }
    } else if (action.type == "PINGFANG") {
        return {
            "a" : state.a * state.a
        }
    }
    return state;
}

//根据reducer函数创建store
const store = redux.createStore(reducer);

//得到store中的a
console.log(store.getState().a);

//发出ADD命令
store.dispatch({"type" : "ADD"}); //10+1
store.dispatch({"type" : "ADD"}); //11+1
store.dispatch({"type" : "ADD"}); //12+1

//得到store中的a
console.log(store.getState().a); //13

//发出减法命令
store.dispatch({"type" : "MINUS"});  // 13-1

//得到store中的a
console.log(store.getState().a); // 12

store.dispatch({ "type": "PINGFANG" }); //12*12
//得到store中的a
console.log(store.getState().a); //144

当dispatch 发出后 store变化了,而试图没有更新,需要使用订阅subscribe方法

1、 在组件内的 componentDidMount 声明周期内 强制刷新

componentDidMount(){
    store.subscribe(()=>{
      this.forceUpdate()
    })
}

2、在入口文件中配置 subscribe,订阅后 重新 执行render

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './store'
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
)

store.subscribe(()=>{
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  )
})

使用redux 检查点

  1. createStore 创建store
  2. reducer 初始化、修改状态函数
  3. getState 获取状态值
  4. dispatch 提交更新
  5. subscribe 变更订阅

可预测状态容器

Redux专门用于管理状态

使用Redux的主要优势之一是它可以帮助处理应用的共享状态,如果两个组件需要访问同一状态,这个两个组件同时需要访问同一状态的现象 称为“共享状态”。你可以将该状态提升到附近的父组件,但是如果该父组件在组件树种向上好几个组件的位置,那么将状态当做属性向下一个一个的传递,这项工作会变的很乏味。此外,在改父组件和子组件之间的组件甚至根本不需要访问该状态!!!

Redux不仅使我们能够有条理的方式存储数据,而且使我们能够在应用的任何位置快速获取该数据。只需要告诉Redux到底哪个组件需要哪个数据,它就会为你处理后续一切工作!

借助Redux,你可以控制状态改变的时间、原因和方式。

Store: 单一数据源

步骤:引入createStore函数、引入reducer统领文件、创建store、试着弹出一个a。

store包括应用的全局状态,全存储在一个对象树种。redux中的状态是只读的,React组件不能直接写入React状态,而是发出intent来更新状态(只有reducer的纯函数能够更改状态)。

store是用来持有state的,一个项目只有一个store,一个store中只有一个reducer(当然可以拆分,然后合并到一个index文件中)

store有下面的责任:
持有app的state
允许通过getState()得到state
允许通过dispatch来改变state
只能有1个store在你的app中。
当你想切分你的数据逻辑,你应该考虑拆分reducer而不是拆分store。
创建一个reducer很简单,使用combineReducers()可以轻松合并多个reducer到一个。并且传入createStore()中

得到Store的State的值,需要使用store.getState().a

复杂的state

state 可能是复杂的结构,此时写在行内不好看,可以提炼为变量(常量)

var redux = require("redux");

//初始state
const initState = {
    students : [
        {"id": 1, "name": "小明" , "age" : 12},
        {"id": 2, "name": "小强" , "age" : 14},
        {"id": 3, "name": "小钢炮" , "age" : 13}
    ]
}

//reducer
const reducer = (state = initState, action) => {
    .........
    .........
}

reducer 纯函数

指的是一个函数接收A、B两个参数,根据B参数返回A参数的新值。 记住4个no: | | ------------------------------------------------------------------------------ | | No surprises. No side effects. No API calls. No mutations. Just a calculation. | | 没有惊喜、没有副作用、没有API调用、没有改变传入的参数。就是一个计算。 |

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

  • 不得改写参数
  • 不能调用写I/O的API
  • 不能调用Date.now() 或者Math.random()等不纯的方法,因为每次得到不一样的结果

Reducer是纯函数,就可以保证同一的state,必定得到同样的View,Reducer函数里面不能改变State,必须返回一个全新的对象。

一个函数会有很多reducer,所以要有一个index.js作为“统领文件”

dispatch和action

唯一能够改变state的方法就是dispatch一个action

store.disptch()

action

  • Action就是一个信息的载荷体,从APP(逻辑层、视图层)送到你的store中(数据层)。
  • action是store唯一的信息的来源。
  • action需要被dispatch()函数派发。
  • action是纯的、扁平的JavaScript对象。
  • action必须有一个type属性,type属性指示了这个action是干嘛的。

action就是一个有type属性的JSON:

{"type" : "ADD"}

payload 载荷

action不仅有type属性,其他的属性,叫做载荷(payload)

combineReducers 合并多个reducer

不同的业务放在多个标准的reducer中,最后用redux.combineReducer()合并一下。

注意:

  • 访问值的时候,多个一个命名空间
  • 改变值的时候,没有命名空间
//要合并reducer,因为任何项目只能有一个reducer。
const reducer = redux.combineReducers({
    counterReducer ,
    studentReducer
});

//store,不管项目有多大,一定只有一个store
const store = redux.createStore(reducer);

//注意,访问值的时候,多了一个命名空间
console.log(store.getState().counterReducer.a);

//但是,改变值的时候,没有命名空间
store.dispatch({ "type" : "ADD"});
store.dispatch({ "type" : "ADD"});
store.dispatch({ "type" : "ADD"});

//注意,访问值的时候,多了一个命名空间
console.log(store.getState().counterReducer.a);

React和Redux配合使用

所有的数据要保存在store中

image.png

配置和使用

  1. 先配置好webpack+react那一套 安装依赖
npm install --save redux
npm install --save react-redux
  • react-redux 是官方的“粘合剂”
    • 这个粘合剂就提供了两个东西:Provider组件、connect函数。
  1. Provider from react-redux

main.js

import React from "react";
import ReactDOM from "react-dom";
import {createStore} from "redux";
import {Provider} from "react-redux";

import App from "./App";
import reducer from "./reducers";

//创建store
const store = createStore(reducer);


ReactDOM.render(
    <Provider store={store}>
        <App></App>
    </Provider>
    ,
    document.getElementById("app")
);

app.js

  • import { connect } from "react-redux"
import React, { Component } from 'react';
import { connect } from "react-redux";

class App extends Component {
    constructor(){
        super();
    }
    render() {
        return (
            <div>
                <h1>根组件{this.props.a}</h1>
                <button onClick={()=>{
                    this.props.add();
                }}>按我加1</button>
            </div>
        )
    }
}
//connect 函数 有两个参数和需要连接的组件App
export default connect(
    ({counterReducer}) => ({
        a : counterReducer.a
    }),
    (dispatch) => ({
        add(){
            dispatch({"type" : "ADD"})
        }
    })
)(App);

Provider

  • Provider 是组件,在main.js入口文件中用一次
  • connect 是函数,哪个组件要“通天”要数据,哪个组件就要connect装饰一下。
  • Provider的机理是使用context上下文机理。

Provider使用起来很简单,引包之后,包裹app,注意store属性:

import {Provider} from "react-redux";

ReactDOM.render(
    <Provider store={store}>
        <App></App>
    </Provider>
    ,
    document.getElementById("app")
);

connect装饰器

语法:

connect(mapStateToProps , mapDispatchToProps)(类名字)
  • 组件要更新视图,只有两种手段:
    • 组件自己的state改变
    • 传入组件的props改变
  • 全局store数据变化了,组件视图要更新。
  • store中的数据,要被“装饰”到组件上,成为组件的props。

组件要被connect()()装饰一下,才能暴露:

  • 第一个()内写如何装饰,第二个()内写装饰谁。
    • 第一个()有两个参数 mapStateToProps和mapDispatchToProps

mapStateToProps(state, ownProps)

  • mapStateToProps 是一个函数,用于建立组件跟store的state的映射关系,它传入两个参数,结果一定返回一个object。
  • mapStateToProps 可以不传,如果不传,组件不会监听store的变化,也就是说Store的更新不会引起UI的更新。
export default connect(
    ({counterReducer}) => ({
        a : counterReducer.a
    })
)(App);

这个函数的形参中罗列reducer的名字,要用大括号包裹,函数返回值是一个对象,所以必须有一个小括号对。

这个返回的对象的所有的键,将成为组件的props。

<h1>根组件{this.props.a}</h1>

mapDispatchToProps

mapDispatchToProps 用于建立组件跟store.dispatch的映射关系,可以是一个objeft,也可以传入一个函数

export default connect(
    ({counterReducer}) => ({
        a: counterReducer.a
    }),
    (dispatch) => ({
        add(){
            dispatch({"type" : "ADD"})
        }
    })
)(App);

logger -- 输出器的安装

  • 一装一引一配 安装依赖
npm install --save redux-logger

main.js中的配置

import React from "react";
import ReactDOM from "react-dom";
import {createStore , applyMiddleware} from "redux";
import {Provider} from "react-redux";
import logger from "redux-logger";
 
import App from "./App";
import reducer from "./reducers";

//创建store
const store = createStore(reducer , applyMiddleware(logger));
 
ReactDOM.render(
    <Provider store={store}>
        <App></App>
    </Provider>
    ,
    document.getElementById("app")
);

今后就能在任何一次dispatch发出的时候,看见控制台的自动输出:能够看见改变前的state、发出的action、改变之后的state。