React基础知识总结(七)

78 阅读15分钟

十八、Redux

18.1 理解Flux架构

在2013年,Facebook让React亮相的同时推出了Flux框架,React的初衷实际上是用来替代jQuery的,Flux实际上就可以用来替代Backbone.jsEmber.js等一系列MVC架构的前端JS框架。

其实FluxReact里的应用就类似于Vue中的Vuex的作用,但是在Vue中,Vue是完整的mvvm框架,而Vuex只是一个全局的插件。

React只是一个MVC中的V(视图层),只管页面中的渲染,一旦有数据管理的时候,React本身的能力就不足以支撑复杂组件结构的项目,在传统的MVC中,就需要用到Model和Controller。Facebook对于当时世面上的MVC框架并不满意,于是就有了Flux, 但Flux并不是一个MVC框架,他是一种新的思想。

image-20220812002755397

  • View: 视图层
  • ActionCreator(动作创造者):视图层发出的消息(比如mouseClick)
  • Dispatcher(派发器):用来接收Actions、执行回调函数
  • Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面

Flux的流程:

  1. 组件获取到store中保存的数据挂载在自己的状态上
  2. 用户产生了操作,调用actions的方法
  3. actions接收到了用户的操作,进行一系列的逻辑代码、异步操作
  4. 然后actions会创建出对应的action,action带有标识性的属性
  5. actions调用dispatcher的dispatch方法将action传递给dispatcher
  6. dispatcher接收到action并根据标识信息判断之后,调用store的更改数据的方法
  7. store的方法被调用后,更改状态,并触发自己的某一个事件
  8. store更改状态后事件被触发,该事件的处理程序会通知view去获取最新的数据

18.2 理解Redux单向数据流流程

React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两个方面,它没涉及。

  • 代码结构
  • 组件之间的通信

2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。

如果你不知道是否需要 Redux,那就是不需要它

只有遇到 React 实在解决不了的问题,你才需要 Redux

简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互,也没有使用 WebSocket
  • 视图层(View)只从单一来源获取数据

需要使用Redux的项目:

  • 用户的使用方式复杂
  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

从组件层面考虑,什么样子的需要Redux:

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

Redux的设计思想:

  1. Web 应用是一个状态机,视图与状态是一一对应的。
  2. 所有的状态,保存在一个对象里面(唯一数据源)。

注意:flux、redux都不是必须和react搭配使用的,因为flux和redux是完整的架构,在学习react的时候,只是将react的组件作为redux中的视图层去使用了。

image-20220812002936378

18.3 redux使用三大原则 -必须掌握

  • Single Source of Truth(唯一的数据源)
  • State is read-only(状态是只读的) - 无法直接修改状态
  • Changes are made with pure function(数据的改变必须通过纯函数完成)
$ cnpm i redux -S

src/13_redux/store/index.js 创建状态管理器

// src/13_redux/store/index.js
// 1.引入 redux 创建 状态管理器的方法
import { createStore } from 'redux' // createStore 方法不被推荐使用 推荐使用 reduxtoolkit
// 2.创建纯函数 reducer  --- 数据的改变必须通过纯函数完成
// 纯函数的参数为 state 以及 action
// state的参数可以设置默认值,默认值即为所有状态的初始化的数据
// action 即为 描述 如何修改状态的对象,有两个参数,固定属性type  以及 约定的属性 payload
// 函数内部依据action.type属性 返回不同的状态
const reducer = (state = {
  bannerList: [],
  proList: [],
  kindList: []
}, action) => {
  switch (action.type) {
    case 'CHANGE_BANNER_LIST':
      return {...state, bannerList: action.payload } // 状态是只读的 - 无法直接修改状态
    case 'CHANGE_PRO_LIST':
      return {...state, proList: action.payload }
    case 'CHANGE_KIND_LIST':
      return {...state, kindList: action.payload }
    default:
      return state
  }
}
// 3.创建状态管理器 --- 唯一的数据源
const store = createStore(reducer)
console.log(store)
/**
 *@@observable: observable()
  dispatch: dispatch(action) // 组件触发修改状态的函数
  getState: getState() // 组件获取状态的函数
  replaceReducer: replaceReducer(nextReducer) // 替换修改状态的reducer
  subscribe: subscribe(listener) // 订阅状态管理器的变化
 */// 4.暴露状态管理器
export default store

src/index.js 配置状态管理器 - 状态管理器修改订阅视图的更新

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 5.引入状态管理器
import store from './13_redux/store'import App from './13_redux/App'const root = ReactDOM.createRoot(document.getElementById('root'));
​
function render () {
  root.render(
    <ErrorBoundary>
      {/* 开启react的严格模式 */}
      <React.StrictMode>
        <App />
      </React.StrictMode>
    </ErrorBoundary>
  );
}
render()
// 6.状态管理器发生改变,订阅
store.subscribe(render) // store的状态发生改变,就会重新执行render函数

src/13_redux/App.jsx使用状态管理器

// src/13_redux/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
​
const App = () => {
  return (
    <div>
      <Home />
      <hr />
      <Kind />
    </div>
  );
};
​
export default App;

src/13_redux/views/Kind.jsx

// src/13_redux/views/Kind.jsx
import React, { useEffect } from 'react';
// 7.组件引入store,通过store.getState()方法获取状态管理器状态,通过store.dispatch 修改状态
import store from '../store'
// kindList
const Kind = () => {
  // const state = store.getState() 
  // console.log(state)// { bannerList:[], proList: [], kindList: [] }
  // const kindList = store.getState().kindList // [] 
  const { kindList } = store.getState() // [] 
​
  useEffect(() => {
    fetch('http://121.89.205.189:3000/api/pro/categorylist')
      .then(res => res.json())
      .then(res => {
        store.dispatch({
          type: 'CHANGE_KIND_LIST',
          payload: res.data
        })
      })
  }, [])
  return (
    <div>
      分类
      <ul>
        {
          kindList && kindList.map((item, index) => {
            return <li key = { index }>{ item }</li>
          })
        }
      </ul>
    </div>
  );
};
​
export default Kind;

src/13_redux/views/Home.jsx

// src/13_redux/views/Home.jsx
import React from 'react';
import { useMount } from 'ahooks'; // 确保安装过 ahooks    cnpm i ahooks -S
// 7.组件引入store,通过store.getState()方法获取状态管理器状态,通过store.dispatch 修改状态
import store from '../store'
// bannerList proList
const Home = () => {
  const { bannerList, proList } = store.getState()
​
  useMount(() => {
    fetch('http://121.89.205.189:3000/api/banner/list').then(res => res.json()).then(res => {
      // 修改状态
      store.dispatch({
        type: 'CHANGE_BANNER_LIST',
        payload: res.data
      })
    })
​
    fetch('http://121.89.205.189:3000/api/pro/list').then(res => res.json()).then(res => {
      // 修改状态
      store.dispatch({
        type: 'CHANGE_PRO_LIST',
        payload: res.data
      })
    })
  })
  return (
    <div>
      首页
      {
        bannerList && bannerList.map(item => {
          return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
        })
      }
      <ul>
        {
          proList && proList.map(item => {
            return (
              <li key = { item.proid }>{ item.proname }</li>
            )
          })
        }
      </ul>
    </div>
  );
};
​
export default Home;

以上代码是最最最最最最最基本的redux的使用,现在企业里基本上不使用这种原始的方法

18.4 redux + react-redux配合使用 -必须掌握

redux是属于js的状态管理模式,不独属于react,在react中需要结合 react-redux 模块使用

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

  • Provider: 提供store <Provider store = { store }><App /></store>

  • connect: 用于连接容器组件和展示组件

    • Provider

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

    • connect

      语法格式为

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

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

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

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

$ cnpm i react-redux -S

18.4.1 创建状态管理器

src/14_redux_react-redux/store/index.js

// src/14_redux_react-redux/store/index.js
import { createStore } from 'redux'const reducer = (state = {
  bannerList: [],
  proList: [],
  kindList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_BANNER_LIST':
      return {...state, bannerList: payload }
    case 'CHANGE_PRO_LIST':
      return {...state, proList: payload }
    case 'CHANGE_KIND_LIST':
      return {...state, kindList: payload }
    default:
      return state
  }
}
​
const store = createStore(reducer)
​
export default store

1.引入 createStore 方法(新版本中建议使用rtk)

2.创建reducer(包含初始化状态,包含了修改状态的标识,返回了新的状态(对象合并))

3.创建store

4.暴露store

18.4.2 使用react-redux

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 5.引入状态管理器 以及 辅助的组件Provider
import { Provider } from 'react-redux'
import store from './14_redux_react-redux/store'import App from './14_redux_react-redux/App'const root = ReactDOM.createRoot(document.getElementById('root'));
​
root.render(
  <ErrorBoundary>
    {/* 开启react的严格模式 */}
    <React.StrictMode>
      {/* 6.组件形式调用状态管理器 */}
      <Provider store={ store }>
        <App />
      </Provider>
    </React.StrictMode>
  </ErrorBoundary>
);
​
​
​

使用了 react-redux 提供的组件组件 Provider 配合 store 完成传递数据

src/14_redux_react-redux/App.jsx

// src/14_redux_react-redux/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
​
const App = () => {
  return (
    <div>
      <Home />
      <hr/>
      <Kind />
    </div>
  );
};
​
export default App;

src/14_redux_react-redux/views/Home.jsx

// src/14_redux_react-redux/views/Home.jsx
import React from 'react';
import { useMount } from 'ahooks'
import { connect } from 'react-redux'const Home = ({ bannerList, proList, getBannerListData, getProListData }) => {
  useMount(() => {
    getBannerListData()
    getProListData()
  })
  return (
    <div>
      首页
      {
        bannerList && bannerList.map(item => {
          return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
        })
      }
      <ul>
        {
          proList && proList.map(item => {
            return (
              <li key = { item.proid }>{ item.proname }</li>
            )
          })
        }
      </ul>
    </div>
  );
};
​
// export default connect(state => {
//   return {
//     bannerList: state.bannerList,
//     proList: state.proList
//   }
// }, dispatch => {
//   return {
//     getBannerListData () {
//       fetch('http://121.89.205.189:3000/api/banner/list')
//         .then(res => res.json())
//         .then(res => {
//           dispatch({
//             type: 'CHANGE_BANNER_LIST',
//             payload: res.data
//           })
//         })
//     },
//     getProListData () {
//       fetch('http://121.89.205.189:3000/api/pro/list')
//         .then(res => res.json())
//         .then(res => {
//           dispatch({
//             type: 'CHANGE_PRO_LIST',
//             payload: res.data
//           })
//         })
//     }
//   }
// })(Home);
// (state) => { return { proList: state.proList }}
// ({ proList }) => { return { proList: proList }}
// ({ proList }) => { return { proList }}
// ({ proList }) => ({ proList })
export default connect(({ bannerList, proList }) => ({ bannerList, proList }), dispatch => ({
  getBannerListData () {
    fetch('http://121.89.205.189:3000/api/banner/list')
      .then(res => res.json())
      .then(res => {
        dispatch({
          type: 'CHANGE_BANNER_LIST',
          payload: res.data
        })
      })
  },
  getProListData () {
    fetch('http://121.89.205.189:3000/api/pro/list')
      .then(res => res.json())
      .then(res => {
        dispatch({
          type: 'CHANGE_PRO_LIST',
          payload: res.data
        })
      })
  }
}))(Home);

src/14_redux_react-redux/views/Kind.jsx 业务逻辑写到容器组件中

// src/14_redux_react-redux/views/Kind.jsx
import React, { useEffect } from 'react';
// 7.通过 react-redux 模块提供的 connect 高阶组件 将这个文件分为 容器组件和展示组件
// Kind 展示数据
// connect(mapStateToProps, mapDispatchToProps)(Kind) 容器组件
// mapStateToProps 函数 有默认参数 state ===》 store.getState() 数据通过props传递给展示组件
// mapDispatchToProps 函数  有默认参数 dispatch  ==> 可以通过 dispatch 触发 reducer 修改状态,可以在此函数中返回 给展示组件 执行的方法import { connect } from 'react-redux'
​
​
const Kind = (props) => {
  const { kindList, getKindListData } = props
  useEffect(() => {
    getKindListData()
  }, [])
  return (
    <div>
      分类
      <ul>
        {
          kindList && kindList.map((item, index) => {
            return <li key = { index }>{ item }</li>
          })
        }
      </ul>
    </div>
  );
};
​
const mapStateToProps = (state) => { // 全局管理的所有的状态
  return { // ==> props.kindList
    kindList: state.kindList
  }
}
​
const mapDispatchToProps = (dispatch) => {
  return {
    getKindListData () { // props.getKindListData()
      fetch('http://121.89.205.189:3000/api/pro/categorylist')
        .then(res => res.json())
        .then(res => {
          dispatch({
            type: 'CHANGE_KIND_LIST',
            payload: res.data
          })
        })
    }
  }
}
​
export default connect(mapStateToProps, mapDispatchToProps)(Kind);

vuex中 可以把异步操作提取到vuex中的actions中,react中异步操作方式会有很多,最常见的就是 redux-thunk

18.5 redux+react-redux分模块使用 - 了解

如果项目很大,或者项目划分比较明确,需要将状态管理器也分模块去处理

假设现在有一个home模块,管理 bannerList 以及 proList

还有一个模块 kind 模块,管理 kindList

src/15_redux_react-redux_combine/store/modules/home.js

// src/15_redux_react-redux_combine/store/modules/home.js
const reducer = (state = {
  bannerList: [],
  proList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_BANNER_LIST':
      return { ...state, bannerList: payload }
    case 'CHANGE_PRO_LIST':
      return { ...state, proList: payload }
    default:
      return state
  }
}
​
export default reducer

src/15_redux_react-redux_combine/store/modules/kind.js

// src/15_redux_react-redux_combine/store/modules/kind.js
const reducer = (state = {
  kindList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_KIND_LIST':
      return { ...state, kindList: payload }
    default:
      return state
  }
}
​
export default reducer

整合多个reducer 为一个,因为 有一个原则 : 单一数据源 的原则

src/15_redux_react-redux_combine/store/index.js

// src/15_redux_react-redux_combine/store/index.js
import { createStore, combineReducers } from "redux";
​
import homeReducer from './modules/home'
import kindReducer from './modules/kind'const reducer = combineReducers({
  home: homeReducer, // home 模块 使用了 homeReducer
  kind: kindReducer // kind 模块 使用了 kindReducer
})
​
const store = createStore(reducer)
​
export default store

入口文件处引入状态管理器

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'import { Provider } from 'react-redux'import store from './15_redux_react-redux_combine/store'
import App from './15_redux_react-redux_combine/App'import ErrorBoundary from './ErrorBoundary'const root = ReactDOM.createRoot(document.getElementById('root'))
​
root.render(
  <ErrorBoundary>
    <Provider store = { store }>
      <App root={ root }/>
    </Provider>
  </ErrorBoundary>
)  
​

组件中使用状态管理器

src/15_redux_react-redux_combine/App.jsx

// src/15_redux_react-redux_combine/App.jsximport React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
​
const App = () => {
  return (
    <div>
      <Home />
      <Kind />
    </div>
  );
};
​
export default App;

src/15_redux_react-redux_combine/views/Home.jsx 业务逻辑在展示型组件中

// src/15_redux_react-redux_combine/views/Home.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Home = (props) => {
  const { bannerList, proList } = props
  useEffect(() => {
    props.getBannerList()
  }, [props])
  return (
    <div>
      2222
      {
          bannerList &&  bannerList.map(item => {
            return (
              <img key = { item.bannerid } src={item.img} alt="" style={{ height: 40 }}/>
            )
          })
        }
        {
          proList &&  proList.map(item => {
            return (
              <li key = { item.proid }> { item.proname } </li>
            )
          })
        }
    </div>
  );
};
// state => { return {}}  <===> state => ({})
// dispatch => { return {}}  <===> dispatch => ({})
export default connect(state => ({ // state = { home: { bannerList: [], proList: []}, kind: { kindList: []}}
  bannerList: state.home.bannerList,
  proList: state.home.proList
}), dispatch => ({
  getBannerList () {
    fetch('http://121.89.205.189:3000/api/banner/list')
      .then(res => res.json())
      .then(res => {
        dispatch({
          type: 'CHANGE_BANNER_LIST',
          payload: res.data
        })
      })
    fetch('http://121.89.205.189:3000/api/pro/list')
      .then(res => res.json())
      .then(res => {
        dispatch({
          type: 'CHANGE_PRO_LIST',
          payload: res.data
        })
      })
  }
}))(Home);

src/15_redux_react-redux_combine/views/Kind.jsx 业务逻辑在容器组件中

// src/15_redux_react-redux_combine/views/Kind.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux' 
const Kind = (props) => {
  const { kindList } = props
  useEffect(() => {
    props.getKindList()
  }, [props])
  return (
    <div>
      333
      {
          kindList &&  kindList.map(item => {
            return (
              <p key = { item }>{ item }</p>
            )
          })
        }
    </div>
  );
};
// (state) => { return { bannerList: state.bannerList, proList: state.proList }}
// (state) => ({ bannerList: state.bannerList, proList: state.proList })
// ({ bannerList, proList }) => ({ bannerList: bannerList, proList: proList })
// ({ bannerList, proList }) => ({ bannerList, proList })// state => ({ kindList: state.kind.kindList })
// ({ kind }) => ({ kindList: kind.kindList })
// ({ kind: { kindList } }) => ({ kindList: kindList })
export default connect(({ kind: { kindList }}) => ({ kindList }), dispatch => ({
  getKindList () {
    fetch('http://121.89.205.189:3000/api/pro/categorylist')
      .then(res => res.json())
      .then(res => {
        dispatch({
          type: 'CHANGE_KIND_LIST',
          payload: res.data
        })
      })
  }
}))(Kind);

细心的你发现,虽然展示组件的代码变少了,但是容器组件中还有 异步相关操作,能否把这些异步操作提取出去

18.6 redux + react-redux + 分模块+ 异步操作 - 了解

配合redux 常用的异步模块 为 redux-thunk, redux-saga

18.6.1 redux-thunk

$ cnpm i redux-thunk -S

src/16_redux_react-redux_redux-thunk_combine/store/modules/home.js

// src/16_redux_react-redux_redux-thunk_combine/store/modules/home.js
const reducer = (state = {
  bannerList: [],
  proList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_BANNER_LIST':
      return { ...state, bannerList: payload }
    case 'CHANGE_PRO_LIST':
      return { ...state, proList: payload }
    default:
      return state
  }
}
​
export default reducer

src/16_redux_react-redux_redux-thunk_combine/store/modules/kind.js

// src/16_redux_react-redux_redux-thunk_combine/store/modules/kind.js
const reducer = (state = {
  kindList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_KIND_LIST':
      return { ...state, kindList: payload }
    default:
      return state
  }
}
​
export default reducer

整合多个reducer 为一个,因为 有一个原则 : 单一数据源 的原则

并且后续需要使用 异步操作,将异步操作模块 使用进来

src/16_redux_react-redux_redux-thunk_combine/store/index.js

// src/16_redux_react-redux_redux-thunk_combine/store/index.js
import { createStore, combineReducers, applyMiddleware } from 'redux'import thunk from 'redux-thunk' // 异步import home from './modules/home'
import kind from './modules/kind'const reducer = combineReducers({ home, kind })
​
const store = createStore(reducer, applyMiddleware(thunk))
​
export default store

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'import { Provider } from 'react-redux'import store from './16_redux_react-redux_redux-thunk_combine/store'
import App from './16_redux_react-redux_redux-thunk_combine/App'import ErrorBoundary from './ErrorBoundary'const root = ReactDOM.createRoot(document.getElementById('root'))
​
root.render(
  <ErrorBoundary>
    <Provider store = { store }>
      <App root={ root }/>
    </Provider>
  </ErrorBoundary>
)  
​

src/16_redux_react-redux_redux-thunk_combine/App.jsx

// src/16_redux_react-redux_redux-thunk_combine/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
​
const App = () => {
  return (
    <div>
      <Home />
      <Kind />
    </div>
  );
};
​
export default App;

上一个案例 异步操作在组件,现在需要将其放到 异步操作模块中去 actionCreator

src/16_redux_react-redux_redux-thunk_combine/store/actions/home.js

// src/16_redux_react-redux_redux-thunk_combine/store/actions/home.jsconst action = {
  getBannerListData (dispatch) {// 函数的默认参数
    fetch('http://121.89.205.189:3000/api/banner/list')
      .then(res => res.json())
      .then(res => {
        dispatch({
          type: 'CHANGE_BANNER_LIST',
          payload: res.data
        })
      })
  },
  // getProListData (dispatch) {
  //   fetch('http://121.89.205.189:3000/api/pro/list')
  //     .then(res => res.json())
  //     .then(res => {
  //       dispatch({
  //         type: 'CHANGE_PRO_LIST',
  //         payload: res.data
  //       })
  //     })
  // }
  // { count: 1 , limitNum: 10}
  getProListData (params) {// 需要传递参数时,返回dispatch的默认参数的函数
    return (dispatch) => {
      fetch('http://121.89.205.189:3000/api/pro/list?limitNum=' + params.limitNum)
        .then(res => res.json())
        .then(res => {
          dispatch({
            type: 'CHANGE_PRO_LIST',
            payload: res.data
          })
        })
    }
  }
}
export default action

src/16_redux_react-redux_redux-thunk_combine/store/actions/kind.js

// src/16_redux_react-redux_redux-thunk_combine/store/actions/kind.jsconst action = {
  getKindListData (dispatch) {
    fetch('http://121.89.205.189:3000/api/pro/categorylist')
      .then(res => res.json())
      .then(res => {
        dispatch({
          type: 'CHANGE_KIND_LIST',
          payload: res.data
        })
      })
  }
}
​
export default action

记住接口有无参数影响 action 的写法,还影响其调用

src/16_redux_react-redux_redux-thunk_combine/views/Home.jsx

// src/16_redux_react-redux_redux-thunk_combine/views/Home.jsx
import React, { useEffect} from 'react';
import { connect } from 'react-redux'
import action from '../store/actions/home'
const Home = (props) => {
  const { bannerList, proList, getBannerList, getProList } = props
  useEffect(() => {
    getBannerList()
    getProList()
  }, [getBannerList, getProList])
  return (
    <div>
      {
          bannerList &&  bannerList.map(item => {
            return (
              <img key = { item.bannerid } src={item.img} alt="" style={{ height: 40 }}/>
            )
          })
        }
        {
          proList &&  proList.map(item => {
            return (
              <li key = { item.proid }> { item.proname } </li>
            )
          })
        }
    </div>
  );
};
​
export default connect(({ home: { bannerList, proList }})=> ({ bannerList, proList }), dispatch => {
  return {
    getBannerList () {
      // 触发异步操作
      dispatch(action.getBannerListData)
    },
    getProList () {
      dispatch(action.getProListData({ limitNum: 5 }))
    }
  }
})(Home);

src/16_redux_react-redux_redux-thunk_combine/views/Kind.jsx 异步在store

// src/16_redux_react-redux_redux-thunk_combine/views/Kind.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
import action from '../store/actions/kind'
const Kind = (props) => {
  const { kindList, getKindList } = props
  useEffect(() => {
    getKindList()
  }, [getKindList])
  return (
    <div>
      {
          kindList &&  kindList.map(item => {
            return (
              <p key = { item }>{ item }</p>
            )
          })
        }
    </div>
  );
};
​
export default connect(({ kind: { kindList }}) => ({ kindList }), dispatch => ({
  getKindList () {
   dispatch(action.getKindListData)
  }
}))(Kind);

虽然引入了redux-thunk,但是仍然可以把 异步放在组件中,具体根据项目的需求而定

创建状态模块

整合模块

创建异步的 actionCreator

组件触发 actionCreator

传递数据以及修改界面

18.6.2 redux-saga

先要了解解决异步操作方案:回调函数、promise、async await、generator yield

es6.ruanyifeng.com/#docs/gener…

$ cnpm i redux-saga -S

src/17_redux_react-redux_redux-saga_combine/store/modules/home.js

// src/17_redux_react-redux_redux-saga_combine/store/modules/home.js
const reducer = (state = {
  bannerList: [],
  proList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_BANNER_LIST':
      return { ...state, bannerList: payload }
    case 'CHANGE_PRO_LIST':
      return { ...state, proList: payload }
    default:
      return state
  }
}
​
export default reducer

src/17_redux_react-redux_redux-saga_combine/store/modules/kind.js

// src/17_redux_react-redux_redux-saga_combine/store/modules/kind.js
const reducer = (state = {
  kindList: []
}, { type, payload }) => {
  switch (type) {
    case 'CHANGE_KIND_LIST':
      return { ...state, kindList: payload }
    default:
      return state
  }
}
​
export default reducer

src/17_redux_react-redux_redux-saga_combine/store/mySaga.js

// src/17_redux_react-redux_redux-saga_combine/store/mySaga.js// call 用来触发异步请求
// put 理解为 原来 store.dispatch
// takeLatest 用来分发告诉程序执行哪一个异步行为
import { call, put, takeLatest } from 'redux-saga/effects'function getBannerListData () {
  return fetch('http://121.89.205.189:3000/api/banner/list').then(res => res.json())
}
function getProListData (params) {
  console.log(params)
  return fetch('http://121.89.205.189:3000/api/pro/list').then(res => res.json())
}
function getKindListData () {
  return fetch('http://121.89.205.189:3000/api/pro/categorylist').then(res => res.json())
}
​
function * getBannerListAction () { // * 代表generator函数,函数内部使用yield继续执行
  const res = yield call(getBannerListData)
  yield put({
    type: 'CHANGE_BANNER_LIST',
    payload: res.data
  })
}
function * getProListAction (action) { // * 代表generator函数,函数内部使用yield继续执行, action理解为参数
  const res = yield call(getProListData, action.payload) // action.payload 即为参数
  yield put({
    type: 'CHANGE_PRO_LIST',
    payload: res.data
  })
}
function * getKindListAction () { // * 代表generator函数,函数内部使用yield继续执行
  const res = yield call(getKindListData)
  yield put({
    type: 'CHANGE_KIND_LIST',
    payload: res.data
  })
}
​
// 分发异步操作
function * mySaga () {
  // takeLatest 第一个参数调用标识,第二个参数是一个函数
  // 有人触发 REQUEST_BANNER_LIST 标识,那么调用 getBannerListAction 函数,不要()
  yield takeLatest('REQUEST_BANNER_LIST', getBannerListAction)
  yield takeLatest('REQUEST_PRO_LIST', getProListAction)
  yield takeLatest('REQUEST_KIND_LIST', getKindListAction)
}
​
export default mySaga

src/17_redux_react-redux_redux-saga_combine/store/index.js

// src/17_redux_react-redux_redux-saga_combine/store/index.js
import { createStore, combineReducers, applyMiddleware } from 'redux'import createSagaMiddleware from 'redux-saga'import mySaga from './mySaga'import home from './modules/home'
import kind from './modules/kind'const reducer = combineReducers({ home, kind })
​
const middleware = createSagaMiddleware()
​
const store = createStore(reducer, applyMiddleware(middleware))
​
middleware.run(mySaga)
​
export default store

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'import { Provider } from 'react-redux'import store from './17_redux_react-redux_redux-saga_combine/store'
import App from './17_redux_react-redux_redux-saga_combine/App'import ErrorBoundary from './ErrorBoundary'const root = ReactDOM.createRoot(document.getElementById('root'))
​
root.render(
  <ErrorBoundary>
    <Provider store = { store }>
      <App root={ root }/>
    </Provider>
  </ErrorBoundary>
)  
​
// src/17_redux_react-redux_redux-saga_combine/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
​
const App = () => {
  return (
    <div>
      <Home />
      <Kind />
    </div>
  );
};
​
export default App;

src/17_redux_react-redux_redux-saga_combine/views/Home.jsx

// src/17_redux_react-redux_redux-saga_combine/views/Home.jsx
import React, { useEffect } from 'react';
import { connect } from 'react-redux'
const Home = (props) => {
  console.log(props)
  const {bannerList, proList, dispatch} = props
  useEffect(() => {
    dispatch({
      type: 'REQUEST_BANNER_LIST'
    })
    dispatch({
      type: 'REQUEST_PRO_LIST',
      payload: { limitNum: 2 }
    })
  }, [dispatch])
  return (
    <div>
       {
          bannerList &&  bannerList.map(item => {
            return (
              <img key = { item.bannerid } src={item.img} alt="" style={{ height: 40 }}/>
            )
          })
        }
        {
          proList &&  proList.map(item => {
            return (
              <li key = { item.proid }> { item.proname } </li>
            )
          })
        }
    </div>
  );
};
​
export default connect(state => {
  return {
    bannerList: state.home.bannerList,
    proList: state.home.proList
  }
})(Home);

src/17_redux_react-redux_redux-saga_combine/views/Kind.jsx

// src/17_redux_react-redux_redux-saga_combine/views/Kind.jsx
import React, { useEffect } from 'react';
import {connect} from 'react-redux'
const Kind = (props) => {
  const { kindList, dispatch} = props
  useEffect(() => {
    dispatch({ type: 'REQUEST_KIND_LIST'})
  }, [dispatch])
  return (
    <div>
        {
          kindList &&  kindList.map(item => {
            return (
              <p key = { item }>{ item }</p>
            )
          })
        }
    </div>
  );
};
​
export default connect(({ kind: { kindList }}) => ({kindList}))(Kind);

18.7 使用immutable不可变数据数据结构 - 了解

immutable不可变的数据解构,意思就是一旦数据被创建,需要使用特殊的方法进行修改(修改过后的数据也是符合immutable数据解构的,他是一个全新的对象)

学习immutable: www.npmjs.com/package/imm…

$ cnpm i immutable redux-immutable -S

重点就是修改 reducer 的数据机构以及组件中获取状态的方式(整合reducer时使用 redux-immutable 提供的 combineReducers)

十九、redux toolkit

rtk

cn.redux.js.org/

19.1 什么是Redux Toolkit

cn.redux.js.org/redux-toolk…

Redux Toolkit 是我们官方的,有观点的,开箱即用的高效 Redux 开发工具集。它旨在成为标准的 Redux 逻辑开发方式,我们强烈建议您使用它。

它包括几个实用程序功能,这些功能可以简化最常见场景下的 Redux 开发,包括配置 store、定义 reducer,不可变的更新逻辑、甚至可以立即创建整个状态的 “切片 slice”,而无需手动编写任何 action creator 或者 action type。它还包括使用最广泛的 Redux 插件,例如 Redux Thunk 用于异步逻辑,而 Reselect 用于编写选择器 selector 函数,因此你可以立即使用它们。

19.2 如何使用redux-toolkit

cn.redux.js.org/tutorials/q…

$ cnpm i @reduxjs/toolkit -S

19.2.1 创建状态管理器

src/15_rtk/store/index.js

// src/15_rtk/store/index.js
// 1.从 Redux Toolkit 引入 configureStore API。
import { configureStore } from '@reduxjs/toolkit'// 2.我们从创建一个空的 Redux store 开始,并且导出它:
export default configureStore({
  reducer: {}
})

19.2.2 入口文件配置状态管理器

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 3.引入状态管理器 以及 辅助的组件Provider
import { Provider } from 'react-redux'
import store from './15_rtk/store'import App from './15_rtk/App'const root = ReactDOM.createRoot(document.getElementById('root'));
​
root.render(
  <ErrorBoundary>
    {/* 开启react的严格模式 */}
    <React.StrictMode>
      {/* 4.组件形式调用状态管理器 */}
      <Provider store={ store }>
        <App />
      </Provider>
    </React.StrictMode>
  </ErrorBoundary>
);
​
​
​

19.2.3 创建分模块切片

创建 slice 需要一个字符串名称来标识切片、一个初始 state 以及一个或多个定义了该如何更新 state 的 reducer 函数。slice 创建后 ,我们可以导出 slice 中生成的 Redux action creators 和 reducer 函数。

src/18_rtk/store/modules/home.js

// src/15_rtk/store/modules/home.js//5. 创建 Redux State Slice// 5.1引入 createSlice API。
import { createSlice } from '@reduxjs/toolkit'
// 5.2创建 slice 需要一个字符串名称来标识切片、一个初始 state 以及一个或多个定义了该如何更新 state 的 reducer 函数。
​
​
export const homeSlice = createSlice({
  name: 'homeList',
  initialState: {
    bannerList: [],
    proList: []
  },
  reducers: {
    changeBannerData (state, action) {
      state.bannerList = action.payload
    },
    changeProData (state, action) {
      state.proList = action.payload
    }
  }
})
​
​
// 5.3 slice 创建后 ,我们可以导出 slice 中生成的 Redux action creators 和 reducer 函数。
export const { changeBannerData, changeProData } = homeSlice.actions
export default homeSlice.reducer

src/18_rtk/store/modules/kind.js

// src/15_rtk/store/modules/kind.js//5. 创建 Redux State Slice// 5.1引入 createSlice API。
import { createSlice } from '@reduxjs/toolkit'
// 5.2创建 slice 需要一个字符串名称来标识切片、一个初始 state 以及一个或多个定义了该如何更新 state 的 reducer 函数。
​
​
export const kindSlice = createSlice({
  name: 'kindList',
  initialState: {
    kindList: []
  },
  reducers: {
    changeKindData (state, action) {
​
      console.log(action)      // { type: '', payload: []}
      state.kindList = action.payload
    }
  }
})
​
console.log(kindSlice)
console.log(kindSlice.actions) // changeKindData
// 5.3 slice 创建后 ,我们可以导出 slice 中生成的 Redux action creators 和 reducer 函数。
export const { changeKindData } = kindSlice.actions
export default kindSlice.reducer

19.2.4 整合切片

src/18_rtk/store/index.js

// src/15_rtk/store/index.js
// 1. 从 Redux Toolkit 引入 configureStore API。
import { configureStore } from '@reduxjs/toolkit'
import kind from './modules/kind'
import home from './modules/home'
// 2.我们创建一个空的 Redux store ,并且导出它:
export default configureStore({
  reducer: {
    // 6.将 Slice Reducers 添加到 Store 中
    kind,
    home
  }
})

19.2.5 组件中使用状态管理器

src/18_rtk/views/Home.jsx

// src/15_rtk/views/Home.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { changeBannerData, changeProData } from '../store/modules/home';
​
const Home = () => {
  const bannerList = useSelector(state => state.home.bannerList)
  const proList = useSelector(state => state.home.proList)
  const dispatch = useDispatch()
  useEffect(() => {
    fetch('http://121.89.205.189:3000/api/banner/list')
      .then(res => res.json())
      .then(res => {
        dispatch(changeBannerData(res.data))
      })
    fetch('http://121.89.205.189:3000/api/pro/list')
      .then(res => res.json())
      .then(res => {
        dispatch(changeProData(res.data))
      })
  }, [dispatch])
  return (
    <div>
      首页
      {
        bannerList && bannerList.map(item => {
          return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
        })
      }
      <ul>
        {
          proList && proList.map(item => {
            return (
              <li key = { item.proid }>{ item.proname }</li>
            )
          })
        }
      </ul>
    </div>
  );
};
​
export default Home;

src/18_rtk/views/Kind.jsx

// src/15_rtk/views/Kind.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux'
import { changeKindData } from './../store/modules/kind'
// 7.在 React 组件中使用 Redux 状态和操作
// 7.1 可以使用 React-Redux 钩子让 React 组件与 Redux store 交互。
// 我们可以使用 useSelector 从 store 中读取数据,使用 useDispatch dispatch actions。
const Kind = () => {
  const kindList = useSelector(state => state.kind.kindList)
  const dispatch = useDispatch()
​
  useEffect(() => {
    fetch('http://121.89.205.189:3000/api/pro/categorylist')
        .then(res => res.json())
        .then(res => {
          dispatch(changeKindData(res.data))
        })
  }, [dispatch])
  return (
    <div>
      分类
      <div>
      分类
      <ul>
        {
          kindList && kindList.map((item, index) => {
            return <li key = { index }>{ item }</li>
          })
        }
      </ul>
    </div>
    </div>
  );
};
​
export default Kind;
  • 使用configureStore创建 Redux store

    • configureStore 接受 reducer 函数作为命名参数
    • configureStore 使用的好用的默认设置自动设置 store
  • 为 React 应用程序组件提供 Redux store

    • 使用 React-Redux <Provider> 组件包裹你的 <App />
    • 传递 Redux store 如 <Provider store={store}>
  • 使用 createSlice 创建 Redux "slice" reducer

    • 使用字符串名称、初始状态和命名的 reducers 函数调用“createSlice”
    • Reducer 函数可以使用 Immer 来“改变”状态
    • 导出生成的 slice reducer 和 action creators
  • 在 React 组件中使用 React-Redux useSelector/useDispatch 钩子

    • 使用 useSelector 钩子从 store 中读取数据
    • 使用 useDispatch 钩子获取 dispatch 函数,并根据需要 dispatch actions

此时发现,组件中的异步操作在 组件内部调用,接下来需要研究如何将异步操作从组件提取出来

19.3 提取异步操作

19.3.1 自己定义请求函数

src/19_rtk_async/api/home.js

// src/16_rtk_async/api/home.js
export function  getBannerListDataAction () {
  return fetch('http://121.89.205.189:3000/api/banner/list').then(res => res.json())
}
​
export function  getProListDataAction () {
  return fetch('http://121.89.205.189:3000/api/pro/list').then(res => res.json())
}

src/19_rtk_async/api/kind.js

// src/16_rtk_async/api/kind.js
export function  getKindListDataAction () {
  return fetch('http://121.89.205.189:3000/api/pro/categorylist').then(res => res.json())
}
​

src/19_rtk_async/store/modules/home.js

// src/16_rtk_async/store/modules/home.jsimport { createSlice } from '@reduxjs/toolkit'
import { getBannerListDataAction, getProListDataAction } from './../../api/home'
export const homeSlice = createSlice({
  name: 'home',
  initialState: {
    bannerList: [],
    proList: []
  },
  reducers: {
    changeBannerData (state, action) {
      state.bannerList = action.payload
    },
    changeProData (state, { payload }) {
      state.proList = payload
    }
  }
})
​
// 异步操作,默认参数为dispatch
export function getBannerListAction (dispatch) {
  getBannerListDataAction().then(res => {
    dispatch(changeBannerData(res.data))
  })
}
// export function getProListAction (dispatch) {
//   getProListDataAction().then(res => {
//     dispatch(changeProData(res.data))
//   })
// }
// 如果需要参数传递,请返回一个自带dispatch参数的函数
export function getProListAction (params) {
  console.log(params)
  return (dispatch) => {
    getProListDataAction().then(res => {
      dispatch(changeProData(res.data))
    })
 }
}
​
export const { changeBannerData, changeProData } = homeSlice.actionsexport default homeSlice.reducer

src/19_rtk_async/store/index.js

// src/16_rtk_async/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import home from './modules/home'
import kind from './modules/kind'
export default configureStore({
  reducer: {
    home, kind
  }
})

src/index.js

// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// 3.引入状态管理器 以及 辅助的组件Provider
import { Provider } from 'react-redux'
import store from './16_rtk_async/store'import App from './16_rtk_async/App'const root = ReactDOM.createRoot(document.getElementById('root'));
​
root.render(
  <ErrorBoundary>
    {/* 开启react的严格模式 */}
    <React.StrictMode>
      {/* 4.组件形式调用状态管理器 */}
      <Provider store={ store }>
        <App />
      </Provider>
    </React.StrictMode>
  </ErrorBoundary>
);
​
​
​

src/19_rtk_async/App.jsx

// src/16_rtk_async/App.jsx
import React from 'react';
import Home from './views/Home';
import Kind from './views/Kind';
​
const App = () => {
  return (
    <div>
      <Home />
      <hr />
      <Kind />
    </div>
  );
};
​
export default App;

src/19_rtk_async/views/Home.jsx

// src/16_rtk_async/views/Home.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getBannerListAction, getProListAction } from '../store/modules/home'
const Home = () => {
  const bannerList = useSelector(state => state.home.bannerList)
  const proList = useSelector(state => state.home.proList)
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(getBannerListAction) // 触发 异步行为,主要不要随意加()
    // dispatch(getProListAction)
    dispatch(getProListAction({ limitNum: 2 }))
  }, [dispatch])
  return (
    <div>
      首页
      {
        bannerList && bannerList.map(item => {
          return <img style={{ height: 60 }} src={ item.img } key = { item.bannerid } alt={ item.alt }/>
        })
      }
      <ul>
        {
          proList && proList.map(item => {
            return (
              <li key = { item.proid }>{ item.proname }</li>
            )
          })
        }
      </ul>
    </div>
  );
};
​
export default Home;

19.3.2 使用 createAsyncThunk 请求数据 - 了解

Redux Toolkit 的 createAsyncThunk API 生成 thunk,为您自动 dispatch 那些 "start/success/failure" action。

src/19_rtk_async/store/modules/kind.js

// src/16_rtk_async/store/modules/kind.jsimport { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { getKindListDataAction } from '../../api/kind'export const kindSlice = createSlice({
  name: 'kind',
  initialState: {
    kindList: []
  },
  reducers: {
    changeKindData (state, action) {
      state.kindList = action.payload
    }
  },
  extraReducers(builder) { // 固定结构
    builder
      .addCase(getKindListAction.pending, (state, action) => {
        state.status = 'loading'
      })
      .addCase(getKindListAction.fulfilled, (state, action) => {
        state.status = 'succeeded'
        // Add any fetched posts to the array
        state.kindList = action.payload
      })
      .addCase(getKindListAction.rejected, (state, action) => {
        state.status = 'failed'
        state.error = action.error.message
      })
  }
})
// createAsyncThunk 两个参数,第一个自定义
// 第二个参数请求数据
export const getKindListAction = createAsyncThunk('kind/getKindListAction', async () => {
  const res = await getKindListDataAction()
  return res.data
})
export const { changeKindData } = kindSlice.actionsexport default kindSlice.reducer
// src/19_rtk_async/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import home from './modules/home'
import kind from './modules/kind'
export default configureStore({
  reducer: {
    home,
    kind
  }
})

src/19_rtk_async/views/Kind.jsx

// src/16_rtk_async/views/Kind.jsx
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { getKindListAction } from '../store/modules/kind';
​
const Kind = () => {
  const kindList = useSelector(state => state.kind.kindList)
  const dispatch = useDispatch()
  useEffect(() => {
    dispatch(getKindListAction()) // 触发异步操作
  }, [dispatch])
  return (
    <div>
      分类
      <ul>
        {
          kindList && kindList.map((item, index) => {
            return <li key = { index }>{ item }</li>
          })
        }
      </ul>
    </div>
  );
};
​
export default Kind;
​

redux

Redux + redux-thunk

redux + react-redux

Redux + react-redux 分模块

redux + react-redux + redux-thunk

redux + react-redux + redux-thunk + 分模块

redux + react-redux + redux-saga

redux + react-redux + redux-saga + 分模块

redux-toolkit

(Redux-thunk\redux-saga\redux-promise....)