Redux

方法一:自己封装的方法

思路:

1. 全局状态值在组件里都能访问到

  * context

  * 单独在内存中存放一个对象来做state的管理

2. 修改数据,

   用户通过dispatch 触发 数据修改函数, 返回新的数据赋值给state

3. 页面更新

   需要的组件通过subscribe 注册一个函数,保存到store对象的lister数组里

   数据改变的时候需要更新 事件分发

解构目录:

首先封装一个全局状态管理的文件store 里面有个index.js

在index.js文件夹下面

function createStore(reducer) {
  // 维护全局状态
  // state, listeners 希望是私有属性 外部不可访问
  let state = undefined;
  let listeners = [];
  // 获取全局状态值得方法
  let getState = () => {
    return state;
  };
  // 事件监听,当组件调用的时候就会把组件的方法存到listeners里面,在下面遍历执行
  let subscribe = (fun) => {
    listeners.push(fun);
  };
  // 触发state的修改
  const dispatch = (action) => {
    console.log("触发dispatch");
    // 修改值后返回结果
    state = reducer(state, action);//若没有改变state就是原始值
    // 数据修改完毕分发通知
    listeners.forEach(listener => listener())
  };

  // 初始化默认数据
  dispatch({ type: "", payload: "" });
  return {
    getState,
    subscribe,
    dispatch,
  };
}

const defaultState = {
  name: "韩梅梅",
  age: 123,

};
// 修改state值
function reducer(prevState = defaultState, action) {
  console.log("reducer执行了");
  console.log("action", action);
  console.log("prevState", prevState);
  let newData = prevState;
  // 对newData做修改
  const { type, payload } = action;
  switch (type) {
    // case "init":
    //   newData = defaultState;
    //   break;
    case "CHANGE_NAME":
      console.log("xxxxxxxx")
      newData.name = payload;
      break;

    default:
      break;
  }
  // 通过action 修改输入
  console.log("reducer数据", newData)
  return newData;
}

// 在任何地方只要能访问到对象就能访问到对象里的数据
const store = createStore(reducer);
export default store;

在son1 里面

import React, { Component } from "react";
import store from './store'
console.log("getState",store.getState())
class Son1 extends Component {
  constructor() {
    super();
  }
  state = {}

  render() {
    return (
    <div>
      <h2>子组件1 {store.getState().name}</h2> //这个地方也是我们可以不用在调用store,直接在高阶组件里面把store里面的state,与dispatsh方法传递到每个组件的props里面,以后在调用的时候就不用再用store了
      
    </div>);
  }

  componentDidMount() {  //在所有组件里面都需要调用store/index里面的subscribe方法所以我们想就可以把这个逻辑直接写在高阶组件里面
    store.subscribe(() => {
      console.log(store.getState())
      this.setState({})
    })
  }
}

export default Son1;

在son1组件使用后,我们发现每个组件都需要调用store里面的subscribe方法,并且每次需要从store里面获取state,与方法,我们想就可以封装一个高阶组件来直接从每个组件的props里面取值与方法

下面是我们自己封装的高阶组件间context.js

上代码


import React, { Component, Fragment } from "react";
import store from "./store";
export default (ContainConponet) => {
  class NewComponent extends Component {
    constructor() {
      super();
    }
    state = {} 
    render() {
      const result = store.getState();
      const fun  = store.dispatch;
      // 获取全局状态值映射到props里
      console.log(fun,'2222');
      return (
      <Fragment>
        <ContainConponet {...result} fun={fun}></ContainConponet>
       {/*  <ContainConponet name={result.name}></ContainConponet> */}{/* 注意在props传值得时候可以直接传递一个对象不用是一个自定义属性={} result是{name: '韩梅梅', age: 123}*/}
      </Fragment>);
    }
    componentDidMount() {
      // 监听
      store.subscribe(() => {
        this.setState({})
      })
    }
  } 
  return NewComponent
}

 在封装完高阶组件完之后,我们在son2.js里面试一下

import React, { Component } from "react";
import connect from "./connect"
//import store from "./store"  用了高阶组件之后就不用store了,可以直接在每个组建的props里面进行取值
@connect
class Son2 extends Component {
  constructor() {
    super();
  }
  state = {}

  render() {
    return (
    <div>
      <h2>子组件2 {this.props.name}</h2>{/* 之前是store.getState().name */}
      <button onClick={() => {
        console.log(this.props);
        /* 之前是store.dispatch */
        this.props.fun({
          type: "CHANGE_NAME",
          payload: "隔壁老王"
        })
      }}>改名</button>
    </div>);
  }
/* 下面的方法放到了高阶组件里面去了,所以不用在写 */
  // componentDidMount() {
  //   store.subscribe(() => {
  //     this.setState({})
  //   })
  // }
}

export default Son2;

接下来就是index.js父组件了,父组件没什么变化,最后再把父组件挂载到APP根组件就好了

import React, { Component } from "react";
import Son1 from "./Son1";
import Son2 from "./Son2";
class Demo extends Component {
  constructor() {
    super();
  }
  state = {}

  render() {
    return (
    <div>
      <h1>自定义全局状态</h1>
      <Son1></Son1>
      <Son2></Son2>
      <Son3></Son3>
    </div>);
  }

  componentDidMount() {}
}

export default Demo;

方法二:通过插件redux

首先 npm  i  redux

首先看下目录解构

首先在index.js创建store 全局状态管理的代码,为了目录清晰,我们写了一个reducer.js进行导入到index.js里面

首先看index.js

import  {createStore} from "redux"; //我们首先使用插件redux里面的createStore方法
import reducer from "./reducer";  //把我们自己写的reducer 文件导入件来
const store = createStore(reducer); //把我们写的项目写入我们引进来的方法里,就相当于我们之前自己封装的createStore方法,用这个插件就不用我们自己写了,但是reducer还是需要我们自己去写
export default store; //最后把store导出就能用了

 在看下reducer.js,在这里面我们还要用到state.js里面是写默认state的值,actionTypes.js 是把所有的传进来的方法名写成一个常量,避免在大型项目的时候,把方法名字写错

我们先来看reducer.js

注意不能修改源数据

因为不能修改原数据,因为修改原数据改变的是对象的引用地址,所以直接修改原数据,引用地址没变,并不能实现更新,不更新就不能触发更新生命周期,也就不能在页面那种渲染出来

例如下面这样,直接修改newData里面的值

 const newData= prevState ;//这样写是不行的,需要进行深拷贝,下面有解决问题的方法

case "changename":
      newData.info.name = "李雷雷";
      return {
        ...prevState,
        info: {
          name: "李雷雷"
        }
      }
      break;

如果实在想改可以先在刚开始实现一下newdata的深拷贝,或者重新赋值一个引用地址,在我的博客immutable实现深浅拷贝_tianruine的博客-CSDN博客我们有更加高效的方法,immutable,或者用loash

const newData = JSON.parse(JSON.stringify(prevState)); 
export default (prevState = defaultState, action) => {
  // const newData= prevState ;//这样写是不行的
  //  不能修改原数据 引用类型修改原数据后 对比值得的改变是对比不出来的
  //  lodash
  const newData = JSON.parse(JSON.stringify(prevState));//方法一

  switch (type) 
    case "changename":
      newData.info.name = "李雷雷";
      // return {  //方法二
      //   ...prevState,
      //   info: {
      //     name: "李雷雷"
      //   }
      // }
      break;

    default:
      break;
  }
  
  return newData;
};

//可以发现与我们之前自己封装写的reducer函数一样
import DefaultState from "./state";
import { CHANGE_AGE, CHANGE_NAME } from "./actionTypes";
export default (prevState = DefaultState, action) => {
  const newData = { ...prevState };
  // 注意不能修改源数据
  const { type, payload } = action;
  switch (type) {
    case CHANGE_NAME:
      newData.name = payload;
      break;
    case CHANGE_AGE:
      newData.age = payload;
      break;
    default:
      break;
  }
  return newData;
};

state.js 默认的state

export default {
  name: "韩梅梅",
  age: 16
}

actionTypes.js  把方法进行写成常量,避免方法过多而写错或者写重复方法名字

const CHANGE_NAME = "CHANGE_NAME"
const CHANGE_AGE = "CHANGE_AGE"
export {
  CHANGE_NAME,
  CHANGE_AGE
}

以上我们就算把store写完了,但是我们还想在每个组件使用方法的时候,直接调用每个组件内部props里面的方法所以我们就写了一个actionCreatore.js,这是把每个组件所用使用的方法都放在这个文件夹里面

actionCreator 本质就是一个对象, 对象的目的是创建action

将action操作做统一的管理

下面是actionCreatore.js

import {CHANGE_AGE, CHANGE_NAME} from "./actionTypes"
export default {
  [CHANGE_NAME](payload) {
    const action = {
      type: CHANGE_NAME,
      payload
    }
    return action
  },
  [CHANGE_AGE](payload) {
    console.log("xxxx")
    const action = {
      type: CHANGE_AGE,
      payload
    }
    return action
  }
}

准备工作做完之后就是各个组件之间的代码了

首先先来看下几个插件

一个是react-redux,npm i react-redux,我们需要用到react-redux里面的connect方法

先看下connect

connect是一个函数

该函数接受2个参数 mapStateToProps, mapDispatchToProps

返回一个高阶组件

mapStateToProps 将state映射到props里 本质可以一个函数

1. 接受到全局状态store的state数据

2. 需要return 一个对象 将映射的对象映射到props里去

const mapStateToProps = (state) => {
  const {name, age } = state
  // console.log("xxxxx----arg",arg)
  return {
    name,
    age
  }
}
//所以可以写成state => state

mapDispatchToProps 将dispatch映射到props 本质也是也是一个函数

1. 如果改参数没有 会默认的将dispatch 映射到props中

2. 接受dispatch作为参数

3. 需要return对象, return的对象会放到props里去

const mapDispatchToProps = (dispatch) => {
  return {
    [CHANGE_AGE]:(age) => {
      const action = actionCreator[CHANGE_AGE](age)
      dispatch(action)
    },
  }
}  //每个函数都需要写成这样,所以我们可以使用bindActionCreators这个属性

简写就这样,会把store里面的方法全部都放到每个组件的props里面 dispatch => bindActionCreators(actionCreator,dispatch)

另一个是bindActionCreators,这个属性是react里面的

bindActionCreators帮助我们映射方法,并且自动的执行dispatch

现在我们来看下组件内部使用的方法

son1.js 

知识点:connect与bindActionCreators,通过connect高阶组件我们就可以拿到全局状态管理里面所有的props与所有方法,调用的时候直接this.props. 进行调用就行

import React, { Component } from "react";
import actionCreator from "./store/actionCreatore"
import { CHANGE_AGE, CHANGE_NAME} from "./store/actionTypes"
// import store from "./store"
import {connect} from "react-redux"
import { bindActionCreators } from "redux";
class Son1 extends Component {
  constructor() {
    super();
  }
  state = {}

  render() {
    return (
    <div>
      组件1
      {/* {store.getState().name} */}
      {this.props.name} 
      {this.props.age} 
      <button onClick={() => {
        // 未使用react-redux之前 dispatch 操作是在actoinCreator中
        // actionCreator[CHANGE_AGE](39)
        this.props[CHANGE_AGE](39);
      }}>改年龄</button>
    </div>);
  }
  componentDidMount() {
    console.log(this)
  }
}
export default connect(state => state, dispatch => bindActionCreators(actionCreator,dispatch))(Son1)

son2.js 我们进行对比,并没有用son1里面那连个插件,

还有个知识点,在组件销毁的时候,要进行对组件监听的事件进行销毁

store.subscribe 在注册一个监听之后 同时会返回一个取消监听的函数,所以在销毁阶段componentWillUnmount进行把返回值销毁

import React, { Component } from "react";
import store from "./store/index"
class Son2 extends Component {
  constructor() {
    super();
  }
  state = {}
  changeName = () => {
    store.dispatch({
      type:"CHANGE_NAME",
      payload: "隔壁老王"
    })
  }
  render() {
    return (
    <div>
      组件2
      {store.getState().name}
      <hr />  
      年龄{store.getState().age}
      <button onClick={this.changeName}>改名</button>
    </div>);
  }

  componentDidMount() {
    // store.subscribe 在注册一个监听之后 同时会返回一个取消监听的函数
   this.unsubscribe =  store.subscribe(() => {
      this.setState({})
    })
  }
  componentWillUnmount() {
    this.unsubscribe &&  this.unsubscribe();
  }
 
}

export default Son2;

在index.js 

在这里有需要用到一个插件Provider,也是react-redux

通过Provider 把store用属性穿进去,这样在每个组件里面才能使用connect高阶组件,每个组件才能通过props取到store里面的属性与方法

上代码,index.js

import React, { Component } from "react";
 import {Provider} from "react-redux" //用Provider属性
import Store from "./store";
import Son1 from "./Son1";
import Son2 from "./Son2";
class Demo extends Component {
  constructor() {
    super();
  }
  state = {
    show: true,
  };
  render() {
    return (
       <Provider store={Store}> //这里通过上下文进行传值
      <div >
        <h1>redux demo</h1>
        {this.state.show && <Son1></Son1>}
        <Son2></Son2>
        <button
          onClick={() => {
            this.setState({ show: false });
          }}
        >
          销毁组件1
        </button>
      </div>
       </Provider>
    );
  }
  componentDidMount() {}
}
export default Demo;

总结:

vuex

vue 一个类mvvm框架

react 视图层的js库 jq view

Flux redux 不止能在react 原生js也能用

* view 视图层

* actionCreator action 动作创建者

* dispatch 派发器

* store  存储应用状态

流程

1. 组件获取store里的状态应用在视图上(1.引入store 2.hoc)

2. 用户发起操作,通过dispatch 派发action

3. actionCreator 接受到用户的操作后 进行逻辑操作,异步操作

4. actionCreator 创建出action

5. 触发的修改数据方法里根据action修改数据

6. store 更新修改后的数据

7. 事件分发通知视图层修改

什么时候使用

redux vuex flux 像眼镜一样 需要的时候就自然就知道了

redux 设计思想

1. web应用的一个状态机 视图与状态一一对应

2. 所有的对象保存在一个状态里统一的数据源

3. 修改数据源的途径必须是统一的

https://www.redux.org.cn/

redux 基本使用

getState

subscribe unsubscribe

dipatch

actionCreator

actionTypes

优化:

1. 取值麻烦 store.getState().name

2. 改值也麻烦

3. 引入文件

react-redux

1. 通过provider提供器将store挂载到根组件的上下文

2. 通过connect将组件进行处理 简化了获取数据 和 监听的操作

redux的异步方法1

我们在上一个的方法中的son1中通过有个setimeout来延时获取props里面的方法,再异步执行这个方法

上代码

import React, { Component } from "react";
import actionCreator from "./store/actionCreatore";
import { CHANGE_AGE, CHANGE_NAME } from "./store/actionTypes";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
class Son1 extends Component {
  constructor() {
    super();
  }
  state = {};
  getData = () => {  //就是定义一个自己的方法,延后延迟获取props里面的方法,就实现了异步
    setTimeout(() => {
      this.props[CHANGE_AGE](39);
    }, 3000);
  };
  render() {
    return (
      <div>
        组件1
        {this.props.name}
        {this.props.age}
        <button
          onClick={() => {
            // 将异步数据请求操作放入业务组件中
             this.getData();
          }}
        >
          改年龄
        </button>
      </div>
    );
  }
  componentDidMount() {
    console.log(this);
  }
}
export default connect(
  (state) => state,
  (dispatch) => bindActionCreators(actionCreator, dispatch)
)(Son1);

redux的异步方法2

处理异步问题的插件

redux-thunk

redux-saga

redux-effect

redux-promise

这里我们用的是redux-thunk,同事还要用到applyMiddleware中间件,是在redux插件里面的属性

代码:在store里面的index.js

import  {createStore, applyMiddleware} from "redux";
import reducer from "./reducer";
import thunk from "redux-thunk" //用到了redux-thunk
const store = createStore(reducer, applyMiddleware(thunk));//把thunk 放到中间件里面
export default store;

另一个改变就是actionCreatore.js里面方法的改动

import store from "./index";
import {CHANGE_AGE, CHANGE_NAME} from "./actionTypes"
export default {
  [CHANGE_NAME](payload) {
      const action = {
        type: CHANGE_NAME,
        payload
      }
      store.dispatch(action)
  },
  [CHANGE_AGE](payload) {
    setTimeout(() => {
      const action = {
        type: CHANGE_AGE,
        payload
      }
      store.dispatch(action) //这个地方不在是直接return action 而是通过store.dispatsh(action),这个地方是异步的处理这个函数
    },3000)
  }
}

在son1调用的时候

import React, { Component } from "react";
import actionCreator from "./store/actionCreatore"
import { CHANGE_AGE, CHANGE_NAME} from "./store/actionTypes"
import store from "./store"
class Son1 extends Component {
  constructor() {
    super();
  }
  state = {}

  render() {
    return (
    <div>
      组件1
      {store.getState().name}
      <button onClick={() => {
        actionCreator[CHANGE_AGE](39) //直接调用就会延迟执行,因为在actionCreator文件中已经执行了异步
      }}>改年龄</button>
    </div>);
  }

  componentDidMount() {
    // store.subscribe 在注册一个监听之后 同时会返回一个取消监听的函数
   this.unsubscribe =  store.subscribe(() => {
      this.setState({})
    })
  }
  componentWillUnmount() {
    this.unsubscribe &&  this.unsubscribe();
    console.log("组件销毁")
  }
}

export default Son1;

异步获取数据实例 

import axios from "axios";
export default {
[GET_PRODUCT_LIST](categoryId) {
    console.log("params", categoryId);
    return async (dispatch) => {
      const res = await axios.get(
        `/hehe/item/list.json?categoryId=${categoryId}&style=pd`
      );
      console.log(res.data.data.categoryItemList);
      dispatch({  //等异步拿到数据之后再抛出,触发reducer里面的case,为GET_PRODUCT_LIST,并把这个数据存到store里面
        type: GET_PRODUCT_LIST,
        payload: res.data.data.categoryItemList[0].itemList,
      });
    };
  }
}

核心: 

中间件其实就是一个函数,中间件允许我们扩展redux应用程序 。具体体现在对action的处理能力上,当组件触发一个action后,这个action会优先被中间件处理,当中间件处理完后,中间件再把action传递给reducer,让reducer继续处理这个action 

 

redux的模块化

首先来看下我们的结构

 我们在store全局管理里面创建了两个模块,分别是info,与list,这两个文件夹里面放的是默认state与对应处理方法的swich语句

例如在info文件下的reduce_info.js里面

import DefaultState from "./state";
import { CHANGE_AGE, CHANGE_NAME } from "../actionTypes";
export default (prevState = DefaultState, action) => {
  console.log("info_reducer")
  const newData = { ...prevState };
  // 注意不能修改源数据
  const { type, payload } = action;
  switch (type) {
    case CHANGE_NAME:
      newData.name = payload;
      break;
    case CHANGE_AGE:
      newData.age = payload;
      break;
    default:
      break;
  }
  return newData;
};

在list文件夹的reducer.js下

const defaultState = {
  list: [1, 2, 3, 4, 5, 6],
}; //默认数据

import { ADD_LIST } from "../actionTypes";
export default (prevState = defaultState, action) => {
  console.log("list-reducer");
  const newData = prevState;
  const { type, payload } = action;
  switch (type) {
    case ADD_LIST:
      
      const list = newData.list
      list.push(payload)
      newData.list = [...list]
      break;

    default:
      break;
  }
  return newData;
};

不同之处是在store的index.js文件夹里

需要用到combineReducers属性是redux的

import  {createStore, combineReducers} from "redux"; //引入combineReducers方法
import InfoReducer from "./info/reducer_info"
import ListReducer from "./list/reducer"
const reducer = combineReducers({ 在这里使用把两个模块合并起来
  info:InfoReducer,
  list:ListReducer
})
const store = createStore(reducer); //把两个都合并起来在放进到store里在导出去
export default store;

在son1.js使用的时候

import React, { Component } from "react";
import {connect} from "react-redux"
import actionCreatore from "./store/actionCreatore";
import { bindActionCreators } from "redux";
import { ADD_LIST } from "./store/actionTypes";
class Son1 extends Component {
  constructor() {
    super();
  }
  state = {};

  render() {
    return <div>
      组件1
      {this.props.name}
      {this.props.age}
      <button onClick={() => {
        // const action =actionCreatore[ADD_LIST](39)
        // console.log(action)
        // this.props.dispatch(action) //上面这些代码,要是不用下面的connect里面的dispatch => bindActionCreators(actionCreatore,dispatch),就按照上面这样写,用来可以直接从props里面拿

        this.props[ADD_LIST](39)
      }}>添加list</button>
    </div>;
  }

  componentDidMount() {
    console.log(this);
  }
}

// export default connect(state => state)(Son1);
//注意这个地方的state要用info里面的,要是不state.info,直接state=>state 拿到的是全部模块的,包括list,与info
export default connect(state => state.info,dispatch => bindActionCreators(actionCreatore,dispatch))(Son1);

看下son2.js 他就是直接用的state,没有用state.list模块,他拿的是所有模块

import React, { Component } from "react";
import {connect} from "react-redux"
import actionCreatore from "./store/actionCreatore";
import {bindActionCreators} from "redux"
import {CHANGE_NAME} from "./store/actionTypes" 
class Son2 extends Component {
  constructor() {
    super();
  }
  state = {};

  render() {
    return <div>
      组件2
     <ul>
       {(this.props.list.list || []).map((item,index)=> {//需要this.props.list模块下的list,因为下面用的全部模块的state
         return <li key={index}>{item}</li>
       })}
     </ul>
     <button onClick={() => {
      //  this.props[CHANGE_NAME]("隔壁老王")
       this.props.CHANGE_NAME('隔壁老王')
     }}>改名</button>
    </div>;
  }
  componentDidMount() {
    console.log(this)
  }
}
//这里直接拿的state全部模块,所以上面遍历的时候需要this.props.list模块下的list
export default connect(state => state, dispatch => bindActionCreators(actionCreatore,dispatch))(Son2);

父组件index.js 没什么改动

import React, { Component } from "react";
import {Provider} from "react-redux"
import Store from "./store";
import Son1 from "./Son1";
import Son2 from "./Son2";
class Demo extends Component {
  constructor() {
    super();
  }
  state = {
    show: true,
  };

  render() {
    return (
      <Provider store={Store}>
      <div>
        <h1>模块化</h1>
        <Son1></Son1>
        <Son2></Son2>
      </div>
      </Provider>
    );
  }

  componentDidMount() {}
}

export default Demo;

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值