知识沉淀--Redux

152 阅读8分钟

Redux

redux是什么?


redux是一个状态管理工具,将react组件中的状态提升到顶层,方便各组件件之间进行数据交互。

为什么需要redux


  1. 在react中数据是单向传递的,也就是我们常说的单项数据流,它只能使用props向下进行传递,使用state在组件内进行数据传递,无法向上传递。
  2. 管理不断变化的state是非常困难的,当应用程序较为复杂时,组件内的state因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪。

由此,产生了状态管理工具,给react提供可预测的状态管理。

Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)

Redux核心概念


在redux中主要包含Store、Action、Reducer三个部分。

Store
  • 作用:存储数据
  • redux中只有一个store
  • 改变store中数据的唯一方法是松果store.dispatch(action.type)
const store = redux.createStore(reducer) //reducer为自定义的纯函数
Action
  • action是一个普通的JavaScript对象,用来描述更新的type和传送的数据;
  • 组件通过派发(dispatch)行为(action)给store,而不是直接通知其它组件;
  • 使用action的好处:可以清晰的知道数据到底发生了什么样的变化,所有的数据变化都是追溯、可预测的

目前我们的action是固定的对象,真实应用中,我们会通过函数来定义,返回一个action;

//way1
const action1 = {type: 'ADD', info:{name:'123',age: '10'}}
//way2
const changeOriginRankingAction = (res) => ({
  type: actionTypes.CHANGE_ORIGIN_RANKING,
  originRanking: res.playlist
})
reducer
  • reducer是一个纯函数,可以使用单独的一个reducer,也可以将多个reducer合并为一个reducer
  • 作用:将传入的state和action结合起来生成一个新的state
function reducer(state = initialState, action) {
  switch (action.type) {
    case "ADD_ FRIEND":
      return { ...state, friends: [...state.friends, action.info] }
    case "INC_ AGE":
      return {
        state, friends: state.friends.map((item, index) => {
          if (index === action.index) {
            return { ...item, age: item.age + 1 }
          }
          return item;
        })
      }
    case "CHANGE_ NAME":
      return {
        ...state, friends: state.friends.map((item, index) => {
          if (index == action.index) {
            return { ...item, name: action.newName }
          }
          return item;
        })
      }
    default:
      return state;
  }
}

Redux执行过程


  • redux简易流程:

redux简易流程

在html页面中,用户发出动作触发action,reducer接收到触发action的类型与数据,对其进行处理,并将处理后的新State传送给Store更新,store将更新后的数据传入react组件中

  • redux详细流程:

redux流程

注:在使用redux的过程中,我们需要对store进行订阅,这样数据才能及时更新

  • 订阅应该在派发(dispatch)前
  • 订阅state发生的改变(通常在useEffect、componentDidmount)
  • 订阅后需要在componentWillUnmount或useEffect中进行取消
unscribe = store.subscribe(()=>{
  console.log('count',store.getState().count)
  //使用setState或useState进行改变试图上的数据
})
//需要在componentWillUnmount或useEffect的回调函数中取消订阅
unscribe()

redux三大原则


单一数据源
  • 整个应用程序的state被存储在一颗object tree中,并且这个object tree只存储在一个 store 中:
  • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护
  • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改
state是只读的
  • 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State
  • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题
使用纯函数进行修改
  • 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State
  • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分
redux的结构划分

如果将所有的逻辑代码写到一起,那么当redux变得复杂时代码就难以维护,因此,有必要对redux的结构进行划分

结构划分

  1. 对代码进行拆分,将store、reducer、action、constants拆分成一个个文件。
  1. 创建store文件夹,存放redux相关代码
  1. 创建store/index.js文件,创建store/reducer.js文件,创建store/actionCreators.js文件,创建store/constants.js文件

    • 在actionCreators.js中写action相关内容
    • 在constants.js中用变量定义action相关的type
    • 在reducer.js中引入constants.js,并写reducer相关代码,再将reducer导出
    • 在index.js中引入redux 、reducer.js,并创建redux.createStore(reducer),并导出store

react-redux


react-redux是什么?

react-redux主要包含两个部分,Provider与Connect

Provider: provider相当于一个顶级组件,通常我们将APP组件包裹在Provider组件中,这样的话,所有组件就都可以在react-redux的控制之下了,但是store必须作为参数放到Provider组件中去

<Provider store = {store}>
    <App />
<Provider>

Provider组件能够让它包裹的组件能够访问到store中的数据

Connect:connect是一个高阶组件,主要形式如下:

connect(mapStateToProps, mapDispatchToProps)(MyComponent)

connect通过mapStateToProps与mapDispatchToProps将store中的数据与方法传入到参数组件的props中

const mapStateToProps = (state) => {
      return {
        // prop : state.xxx  | 意思是将state中的某个数据映射到props中
        foo: state.bar
      }
    }
​
const mapDispatchToProps = (dispatch) => { // 默认传递参数就是dispatch
  return {
    onClick: () => {
      dispatch({
        type: 'increatment'
      });
    }
  };
}
​
//渲染的时候就可以使用this.props.foo 与this.props.onClick
class Foo extends Component {
    constructor(props){
        super(props);
    }
    render(){
        return(
          // 这样子渲染的其实就是state.bar的数据了
            <div>this.props.foo</div>
            <button onClick = {this.props.onClick}>点击increase</button>
        )
    }
}
Foo = connect()(Foo);
export default Foo;

为什么需要react-redux

  • react-redux可以借助connect高阶函数防止一些冗余的、重复的代码,例如:

    • 避免在每个组件中重复书写订阅更新、
    • 在订阅回调中: 1、获取当前 store 状态, 2、提取此 UI 所需的数据,3、用数据更新 UI

redux和react没有直接的关系,可以在React, Angular, Ember, jQuery, or vanilla JavaScript中 使用Redux。

  • connect简易源码如下:

connect

redux中的中间件


  • 由于reducer是一个纯函数,不能在其中发起网络请求等具有副作用的操作,因此,在进行这类操作时我们需要借助中间件。
  • 中间件的目的是在dispatch的action和最终达到的reducer之间,扩展一些自己的代码,比如日志记录、调用异步接口、添加代码调试功能等等。
  • 常见的redux中间件有redux-thunk、redux-promise、redux-saga

redux-thunk

reducer为纯函数不能发起网络请求,redux-thunk中间件通过在action中实现异步操作来发起网络请求,主要原理是在action 中发起异步请求,等待请求的数据返回来后,在触发dispatch(action函数),将返回得分数据作为action函数的参数传给action

  • 该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数

    • dispatch函数用于我们之后再次派发action
    • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态

redux-thunk的应用

  • 作用:完成redux中的异步操作
  • yarn add redux-thunk
  • 在创建store时传入应用了middleware的enhance函数
  • 将enhancer作为第二个参数传入到createStore中;
const enhancer = applyMiddleware(thunkMiddleWare)
const store = createStore(reducer,enhancer)
  • 定义返回一个函数的action,此时的action不是返回对象而是函数
  • 该函数在dispatch后会被执行
const getHomeMultidataAction = () => {
  return (dispatch) => {
    axios.get('url').then(res => {
      dispatch(changeDataAction(data.list))
    })
  }
}

redux-promise

redux-promise相对redux-thunk更简单,它直接把异步操作作为actionpayload,当异步任务有了结果后自动再触发该action,进入到reducer更新state

redux-promise的使用

  1. 安装npm i redux-promise
  2. 配置redux-promise中间件
import promiseMiddleware from "redux-promise";
​
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  redicers,
  composeEnhancers(applyMiddleware(promiseMiddleware))
);
  1. redux-promise的使用
import { getTodoByIdType } from "../types";
​
// 请求API
const getTodoById = async (payload) => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${payload}`
  );
  const response = await res.json();
  return response;
};
​
// 直接将异步请求设置成payload
// 当异步有了结果会自动触发该action,然后进入到reducer更新state
export const getTodoByIdPromiseAction = (payload) => {
  return {
    type: getTodoByIdType,
    payload: getTodoById(payload),
  };
};

React组件dispatch(getTodoByIdPromiseAction(1))后会进入到getTodoByIdPromiseAction方法进行异步请求,当请求返回结果后把异步结果作为payload再触发同步actiongetTodoByIdPromiseAction,进入到相应reducer更新state

redux-saga

Redux-saga是另一个比较常用在redux发送异步请求的中间件,它的使用更加的灵活,主要原理是将redux中的异步请求抽取出来建立一个saga文件

redux-saga的应用

  1. 安装 npm i redux-saga
  2. redux-saga单独把异步任务抽取出来了,所以需要有单独的saga文件来处理异步任务。我们创建一个sagas文件夹,里面存放saga文件。
  3. 建立saga.js文件
import { call, takeEvery, takeLatest, put, select } from "redux-saga/effects";
import { getTodoByIdType, fetchTodoByIdType } from "../types";
​
// 异步接口
const getTodoById = async (payload) => {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${payload}`
  );
  const response = await res.json();
  return response;
};
​
// dispatch 的 action 会被作为参数自动接收
function* fetchTodo(action) {
  try {
    // 调用接口方法
    const todo = yield call(getTodoById, action.payload);
    console.log("todo", todo);
​
    // 获取state
    let item1 = yield select((state) => state.todo.item);
    console.log(item1);
​
    // 提交 action
    yield put({ type: getTodoByIdType, payload: todo });
​
    // 获取state
    let item2 = yield select((state) => state.todo.item);
    console.log(item2);
  } catch (e) {
    // 请求错误置空处理
    yield put({ type: getTodoByIdType, payload: {} });
  }
}
​
// 监听 我们 fetchTodoByIdType的action,当监听后会自动执行fetchTodo方法
function* listenGetTodo() {
  yield takeEvery(fetchTodoByIdType, fetchTodo); // 始终监听
  // yield takeLatest(fetchTodoByIdType, fetchuserr); // 监听最新的一次
}
​
// 使用数组导出,当有多个方法需要监听的时候放在数组即可
const saga1 = [listenGetTodo()];
​
export default saga1;
  1. rootSaga用来组合所有的saga文件,类似combineReducers
// sagas/index.jsimport { all } from "redux-saga/effects";
import saga1 from "./saga1";
import saga2 from "./saga2";
​
function* rootSaga() {
  // 多个saga
  yield all([...saga1, ...saga2]);
}
​
export default rootSaga;
  1. 以中间件形式配置使用,并启动saga
import createSagaMiddleware from "redux-saga";
import rootSaga from "./sagas/index";
​
const sagaMiddleware = createSagaMiddleware();
​
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  redicers,
  composeEnhancers(applyMiddleware(sagaMiddleware))
);
​
// 启动saga,相当于监听
sagaMiddleware.run(rootSaga);

参考文章


juejin.cn/post/684490…

juejin.cn/post/710530…

还参考了两个文章,忘记记下链接了(因为有部分内容是很久之前的md笔记,当初没想过要发到网上来,就没有记录链接,后续找到会放上来)