Redux基本使用

Redux基本使用

什么是Redux?

    1. Redux是一个管理状态(数据)的容器,提供了可预测的状态管理
    1. 什么是可预测的状态管理?
    • 数据在什么时候,因为什么,发生了什么改变,都是可以控制和追踪的,我们就称之为预测的状态管理
    1. 为什么要使用Redux?
    • React是通过数据驱动界面更新的,React负责更新界面, 而我们负责管理数据
    • 默认情况下我们可以在每个组件中管理自己的状态, 但是现在前端应用程序已经变得越来越复杂
    • 状态之间可能存在依赖关系(父子、共享等),一个状态的变化会引起另一个状态的变化
    • 所以当应用程序复杂的时候, 状态在什么时候改变,因为什么改变,发生了什么改变,就会变得非常难以控制和追踪
    • 所以当应用程序复杂的时候,我们想很好的管理、维护、追踪、控制状态时, 我们就需要使用Redux
    1. Redux核心理念
    • 通过 store 来 保存数据
    • 通过 action 来 修改数据
    • 通过 reducer 来 关联 store 和 action

Redux核心理念

三大原则

    1. Redux三大原则
    • 单一数据源
      • 整个应用程序的state只存储在一个 store
      • Redux并没有强制让我们不能创建多个Store,但是那样做并不利于数据的维护
      • 单一的数据源可以让整个应用程序的state变得方便维护、追踪、修改
    • State是只读的
      • 唯一修改State的方法一定是触发action,不要试图在其他地方通过任何的方式来修改State
      • 这样就确保了View或网络请求都不能直接修改state,它们只能通过action来描述自己想要如何修改state;
      • 这样可以保证所有的修改都被集中化处理,并且按照严格的顺序来执行,所以不需要担心race condition(竟态)的问题;
      • 使用纯函数来执行修改
        • 通过reducer将 旧state和 action联系在一起,并且返回一个新的State:
        • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分
        • 但是所有的reducer都应该是纯函数,不能产生任何的副作用
    1. 什么是纯函数
      返回结果只依赖于它的参数,并且在执行过程里面没有副作用
// 纯函数
function sum(num1, num2){
  return num1 + num2;
}
 
// 非纯函数
let num1 = 10;
function sum(num2){
  return num1 + num2;
}
 
// 纯函数
const num1 = 10;
function sum(num2){
  return num1 + num2;
}

基本使用v1

  • 准备工作

    • 创建 demo 目录
    • cd demo
    • npm init -y # 初始化一个node项目
    • npm install –save redux # 安装redux
  • redux的使用

    • store.subscribe() # 监听函数(一旦 state 发生变化,就自动执行这个函数)

    • store.getState() # 获取当前的 state

    • store.dispatch() # 派发 action(用于修改state)

import { createStore } from 'redux'

const initState = {}

const addAction = { type: 'ADD_COUNT', num: 1 }
const subAction = { type: 'SUB_COUNT', num: 1 }

function reducer(state = initState, action) {
  switch(action.type) {
    case 'ADD_COUNT':
      return { count: state.count + action.num };
    case 'SUB_COUNT':
      return { count: state.count - action.num};
    default:
      return state
  }
}

const store = createStore(reducer)

// 使用 监听state更新 store.subscribe会返回一个Func,这个Func可以用来取消订阅
const unSubscribe = store.subscribe(() => {
  console.log(store.getState()) // 获取state中的最新数据
})

// 取消监听
unSubscribe()

// 修改state中的数值
store.dispatch(addAction)
store.dispatch(subAction)
  • 当前代码存在的问题
    • 1.store、action、reducer代码都写在一个文件中,不利于维护
    • 2.action和reducer中都是使用字符串,来指定和判断操作类型, 写错不报错
    • 3.action中的操作写死了,不够灵活

优化v2

import redux from 'redux'

const initState = {}

const ADD_COUNT = 'ADD_COUNT'
const SUB_COUNT = 'SUB_COUNT'

const addAction = (num) => {
  return { type: 'ADD_COUNT', num: num }
} 
const subAction = (num) => {
  return { type: 'SUB_COUNT', num: num }
} 

function reducer(state = initState, action) {
  switch(action.type) {
    case ADD_COUNT:
      return { count: state.count + action.num };
    case SUB_COUNT:
      return { count: state.count - action.num};
    default:
      return state
  }
}

const store = redux.createStore(reducer)

// 使用 监听state更新 store.subscribe会返回一个Func,这个Func可以用来取消订阅
const unSubscribe = store.subscribe(() => {
  console.log(store.getState()) // 获取state中的最新数据
})

// 取消监听
unSubscribe()

// 修改state中的数值
store.dispatch(addAction)
store.dispatch(subAction)

优化v3 工程化

redux_1

结合React,在src下创建store文件夹

store/constants.js

// 定义常量
export const ADD_COUNT = 'ADD_COUNT',
export const SUB_COUNT = 'SUB_COUNT'

sotre/index.js

import {createStore} from 'redux';
import reducer from './reducer';
 
// 利用store来保存状态(state)
const store = createStore(reducer);
 
export default store;

store/reducer.js

import { ADD_COUNT, SUB_COUNT } from "./constants";

// 初始化状态
const initState = {
  count: 0
}

function reducer(state = initState, action) {
  switch(action.type) {
    case ADD_COUNT:
      return { count: state.count + action.num }
    case SUB_COUNT:
      return { count: state.count - action.num }
    default:
      return state
  }
}

export default reducer

store/actions.js

import { ADD_COUNT, SUB_COUNT } from "./constants";

export const addAction = (num) => {
  return { type: ADD_COUNT, num: num }
}

export const subAction = (num) => {
  return { type: SUB_COUNT, num: num }
}

/src/About.js

import React, { Component } from 'react'
import store from '../store'
import { addAction, subAction } from '../store/actions'

class About extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: store.getState().count
    }
  }
  componentDidMount() {
    this.noSubscribe = store.subscribe(() => {
      // 监听store
      this.setState({
        count: store.getState().count
      })
    })
  }
  componentWillUnmount() {
    this.noSubscribe() // 移除监听
  }

  handleAdd = () => {
    store.dispatch(addAction(1))
  }

  render() {
    const { count } = this.state
    return (
      <div>
        <h3>About页面</h3>
        <p>Count:{count}</p>
        <button onClick={this.handleAdd}>增加1</button>
      </div>
    )
  }
}

export default About

src/Test.js

import React, { Component } from 'react'
import store from '../store'
import { subAction } from '../store/actions'

class Test extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: store.getState().count
    }
  }
  componentDidMount() {
    store.subscribe(() => {
      this.setState({ count: store.getState().count })
    })
  }
  handleSub = () => {
    store.dispatch(subAction(1))
  }
  render() {
    const { count } = this.state
    return (
      <div>
        <h3>Test页面</h3>
        <p>Count:{count}</p>
        <button onClick={this.handleSub}>减少1</button>
      </div>
    )
  }
}

export default Test
  • 存在问题:
    • 1.冗余代码太多, 每次使用都需要在构造函数中获取
    • 2.每次使用都需要监听和取消监听
    • 3.操作store的代码过于分散
  • 如何优化:
    • 如何解决冗余代码太多问题
      • 使用 React-Redux
    • 什么是 React-Redux
      • React-Redux是Redux官方的绑定库,能够让我们在组件中更好的读取和操作Redux 保存的状态

优化v4 react-redux

npm install --save react-redux

src/About.js

import React, { Component } from 'react'
import { addAction } from '../store/actions'
import { connect } from 'react-redux'

class About extends Component {
  constructor(props) {
    super(props)
    this.state = {}
  }

  render() {
    const { count } = this.props
    return (
      <div>
        <h3>About页面</h3>
        <p>Count:{count}</p>
        <button onClick={() => this.props.handleAdd(1)}>增加1</button>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    handleAdd(num) {
      dispatch(addAction(num))
    }
  }
}

export default connect(mapStateToProps,mapDispatchToProps)(About)

src/Test.js

import React, { Component } from 'react'
import { subAction } from '../store/actions'
import { connect } from 'react-redux'

class Test extends Component {
  constructor(props) {
    super(props)
    this.state = {}
  }
  render() {
    const { count } = this.props
    return (
      <div>
        <h3>Test页面</h3>
        <p>Count:{count}</p>
        <button onClick={() => this.props.handleSub(1)}>减少1</button>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    count: state.count
  }
}
const mapDispatchToProps = (dispatch) => {
  return {
    handleSub(num) {
      dispatch(subAction(num))
    }
  }
}

export default connect(mapStateToProps,mapDispatchToProps)(Test)

优化v5 网络请求

Store/constants.js

// 定义常量
export const ADD_COUNT = 'ADD_COUNT'
export const SUB_COUNT = 'SUB_COUNT'
export const SET_INFO = 'SET_INFO'

Store/actions.js

import { ADD_COUNT, SET_INFO, SUB_COUNT } from "./constants";

export const addAction = (num) => {
  return { type: ADD_COUNT, num: num }
}

export const subAction = (num) => {
  return { type: SUB_COUNT, num: num }
}

export const setInfo = (info) => {
  return { type: SET_INFO, payload: info}
}

Store/reducer.js

import { ADD_COUNT, SUB_COUNT, SET_INFO } from "./constants";

// 初始化状态
const initState = {
  count: 0,
  info: {}
}

function reducer(state = initState, action) {
  switch(action.type) {
    case ADD_COUNT:
      return { ...state, count: state.count + action.num }
    case SUB_COUNT:
      return { ...state, count: state.count - action.num }
    case SET_INFO:
      console.log(action)
      return { ...state, info: action.payload }
    default:
      return state
  }
}

export default reducer

components/About.js

import React, { Component } from 'react'
import { addAction, setInfo } from '../store/actions'
import { connect } from 'react-redux'

class About extends Component {
  constructor(props) {
    super(props)
    this.state = {}
  }

  componentDidMount() {
    setTimeout(() => {
      const info = {
        name: '无敌',
        age: 18,
        address: '中国'
      }
      this.props.changeInfo(info)
    },2000)
  }
  render() {
    const { count, info = {} } = this.props
    return (
      <div>
        <h3>About页面</h3>
        <p>Count:{count}</p>
        <button onClick={() => this.props.handleAdd(1)}>增加1</button>
        <div>个人信息:
          <p>名字:{info.name}</p>
          <p>年龄:{info.age}</p>
          <p>地址:{info.address}</p>
        </div>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    count: state.count,
    info: state.info
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    handleAdd(num) {
      dispatch(addAction(num))
    },
    changeInfo(info) {
      dispatch(setInfo(info))
    }
  }
}

export default connect(mapStateToProps,mapDispatchToProps)(About)
  • 当前保存异步数据存在的问题

    • 异步数据的获取,可以选择性的放到redux中,而不是生命周期中。当然两者并没有谁对谁错,具体看需求而定!
  • 如何在Redux中 获取网络数据

    • 使用 redux-thunk 中间件
  • redux-thunk 作用

    • 默认情况下 dispatch 只能接收一个对象
    • 使用 redux-thunk 可以让 dispatch 除了可以接收一个对象以外, 还可以接收一个函数
    • 通过 dispatch 派发一个函数的时候能够去执行这个函数, 而不是执行 reducer 函数
  • redux-thunk 如何使用

    • 安装 redux-thunk

      npm install --save redux-thunk
    • 在创建 store 时应用 redux-thunk 中间件

    • 在 action 中获取网络数据

    • 在组件中派发 action

redux-thunk

优化 v6 redux-thunk

redux_2

store/index.js

import { createStore, applyMiddleware } from 'redux';
import reducer from './reducer';
import ThunkMiddleware from 'redux-thunk';
 
const storeEnhancer = applyMiddleware(ThunkMiddleware)
// 利用store来保存状态(state)
const store = createStore(reducer, storeEnhancer);
 
export default store;

store/action.js

import { ADD_COUNT, SET_INFO, SUB_COUNT, SET_LOADING } from "./constants";

export const addAction = (num) => {
  return { type: ADD_COUNT, num: num }
}

export const subAction = (num) => {
  return { type: SUB_COUNT, num: num }
}

export const setInfo = (info) => {
  return { type: SET_INFO, payload: info}
}
export const setLoading = (isLoading) => {
  return { type: SET_LOADING, payload: isLoading}
}

export const getInfo = (dispatch,getState) => {
  dispatch(setLoading(true))
  setTimeout(() => {
    dispatch(setInfo({
      name: '无敌',
      age: '999',
      address: '花果山'
    }))
    dispatch(setLoading(false))
  },2000)
}

Store/constants.js

// 定义常量
export const ADD_COUNT = 'ADD_COUNT'
export const SUB_COUNT = 'SUB_COUNT'
export const SET_INFO = 'SET_INFO'
export const SET_LOADING = 'SET_LOADING'

components/About.js

import React, { Component } from 'react'
import { addAction, setInfo, getInfo } from '../store/actions'
import { connect } from 'react-redux'

class About extends Component {
  constructor(props) {
    super(props)
    this.state = {}
  }

  componentDidMount() {}
  render() {
    const { count, info = {}, loading } = this.props
    return (
      <div>
        <h3>About页面{loading && 'loading...'}</h3>
        <p>Count:{count}</p>
        <button onClick={() => this.props.handleAdd(1)}>增加1</button>
        <div>个人信息:
          <p>名字:{info.name}</p>
          <p>年龄:{info.age}</p>
          <p>地址:{info.address}</p>
        </div>
        <button onClick={this.props.handleUpdateInfo}>查询用户信息</button>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    count: state.count,
    info: state.info,
    loading: state.isLoading
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    handleAdd(num) {
      dispatch(addAction(num))
    },
    changeInfo(info) {
      dispatch(setInfo(info))
    },
    handleUpdateInfo() {
      // 默认情况下 dispatch只能接收一个对象
      // 如果想让dispatch方法可以接收一个对象以外,还可以接收一个方法 这时我们可以使用redux-thunk
      // redux-thunk可以让dispatch接收一个函数,让我们在通过dispatch派发任务时,去执行我们传入的方法
      // 在dispatch时执行异步的getInfo方法
      dispatch(getInfo)
    }
  }
}

export default connect(mapStateToProps,mapDispatchToProps)(About)

优化 v7 combineReducers

一般比较大的项目中,我们这样做所有的actions,所有的reducer都放到一个文件中还是比较臃肿的,因为我们需要进行按模块化拆分

因此我们可以使用 combineReducers,它的作用是将多个reducer进行整合,合并所有零散的reducer为一个大的reducer,

store/reducers/index.js

import { combineReducers } from 'redux'

import test from './test.js'
import about from './about.js'

const rootReducer = combineReducers({test,about})
export default rootReducer

store/reducers/about.js

import { SET_INFO, SET_LOADING } from "../constants";

// 初始化状态
const initState = {
  info: {},
  isLoading: false
}

// eslint-disable-next-line import/no-anonymous-default-export
export default function(state = initState, action) {
  switch(action.type) {
    case SET_INFO:
      return { ...state, info: action.payload }
    case SET_LOADING:
      return { ...state, isLoading: action.payload }
    default:
      return state
  }
}

store/reducers/test.js

import { ADD_COUNT, SUB_COUNT } from '../constants'
let initState = {
  count: 0
}

// eslint-disable-next-line import/no-anonymous-default-export
export default function(state = initState, action) {
  switch(action.type) {
    case ADD_COUNT:
      return { ...state, count: state.count + action.num }
    case SUB_COUNT:
      return { ...state, count: state.count - action.num }
    default:
      return state
  }
}

完整基础demo

  • 目录结构:

    redux_3

  • 代码块

    src/components/About.js

    import React, { Component } from 'react'
    import { setInfo, getInfo } from '../store/actions/about'
    import { connect } from 'react-redux'
    
    class About extends Component {
      getLoadingDom = (str) => {
        const { loading } = this.props
        return loading ? 'loading...' : str
      }
      render() {
        const { info = {}, handleUpdateInfo } = this.props
        return (
          <div>
            <h3>About页面</h3>
            <div>个人信息:
              <p>名字:{this.getLoadingDom(info.name)}</p>
              <p>年龄:{this.getLoadingDom(info.age)}</p>
              <p>地址:{this.getLoadingDom(info.address)}</p>
            </div>
            <button onClick={handleUpdateInfo}>查询用户信息</button>
          </div>
        )
      }
    }
    
    const mapStateToProps = (state) => {
      return {
        info: state.about.info,
        loading: state.about.isLoading
      }
    }
    
    const mapDispatchToProps = (dispatch) => {
      return {
        changeInfo(info) {
          dispatch(setInfo(info))
        },
        handleUpdateInfo() {
          // 默认情况下 dispatch只能接收一个对象
          // 如果想让dispatch方法可以接收一个对象以外,还可以接收一个方法 这时我们可以使用redux-thunk
          // redux-thunk可以让dispatch接收一个函数,让我们在通过dispatch派发任务时,去执行我们传入的方法
          // 在dispatch时执行异步的getInfo方法
          dispatch(getInfo)
        }
      }
    }
    
    export default connect(mapStateToProps,mapDispatchToProps)(About)

    src/components/Test.js

    import React, { Component } from 'react'
    import { subAction } from '../store/actions/test'
    import { connect } from 'react-redux'
    
    class Test extends Component {
      render() {
        const { count } = this.props
        return (
          <div>
            <h3>Test页面</h3>
            <p>Count:{count}</p>
            <button onClick={() => this.props.handleSub(1)}>减少1</button>
          </div>
        )
      }
    }
    
    const mapStateToProps = (state) => {
      return {
        count: state.test.count
      }
    }
    const mapDispatchToProps = (dispatch) => {
      return {
        handleSub(num) {
          dispatch(subAction(num))
        }
      }
    }
    
    export default connect(mapStateToProps,mapDispatchToProps)(Test)

    src/store/actions/about.js

    import { SET_INFO, SET_LOADING } from "../constants";
    
    export const setInfo = (info) => {
      return { type: SET_INFO, payload: info}
    }
    export const setLoading = (isLoading) => {
      return { type: SET_LOADING, payload: isLoading}
    }
    
    export const getInfo = (dispatch,getState) => {
      dispatch(setLoading(true))
      setTimeout(() => {
        dispatch(setInfo({
          name: '无敌',
          age: '999',
          address: '花果山'
        }))
        dispatch(setLoading(false))
      },2000)
    }

    src/store/actions/test.js

    import { ADD_COUNT, SUB_COUNT } from "../constants";
    
    export const addAction = (num) => {
      return { type: ADD_COUNT, num: num }
    }
    
    export const subAction = (num) => {
      return { type: SUB_COUNT, num: num }
    }

    src/store/reducers/about.js

    import { SET_INFO, SET_LOADING } from "../constants";
    
    // 初始化状态
    const initState = {
      info: {},
      isLoading: false
    }
    
    // eslint-disable-next-line import/no-anonymous-default-export
    export default function(state = initState, action) {
      switch(action.type) {
        case SET_INFO:
          return { ...state, info: action.payload }
        case SET_LOADING:
          return { ...state, isLoading: action.payload }
        default:
          return state
      }
    }
    

    src/store/reducers/test.js

    import { ADD_COUNT, SUB_COUNT } from '../constants'
    let initState = {
      count: 0
    }
    
    // eslint-disable-next-line import/no-anonymous-default-export
    export default function(state = initState, action) {
      switch(action.type) {
        case ADD_COUNT:
          return { ...state, count: state.count + action.num }
        case SUB_COUNT:
          return { ...state, count: state.count - action.num }
        default:
          return state
      }
    }

    src/store/reducers/index.js

    import { combineReducers } from 'redux'
    // 自动检索reducers文件夹,导入除index以外的其他文件
    const modulesFiles = require.context('./',true,/[^index]\.js$/)
    const modules = modulesFiles.keys().reduce((modules,modulePath) => {
      const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/,'$1')
      const value = modulesFiles(modulePath)
      modules[moduleName] = value.default
      return modules
    }, {})
    
    const rootReducer = combineReducers(modules)
    
    export default rootReducer

    src/store/Constants.js

    // 定义常量
    export const ADD_COUNT = 'ADD_COUNT'
    export const SUB_COUNT = 'SUB_COUNT'
    export const SET_INFO = 'SET_INFO'
    export const SET_LOADING = 'SET_LOADING'

    src/store/index.js

    import { createStore, applyMiddleware } from 'redux';
    import reducer from './reducers/index';
    import ThunkMiddleware from 'redux-thunk';
     
    const storeEnhancer = applyMiddleware(ThunkMiddleware)
    // 利用store来保存状态(state)
    const store = createStore(reducer, storeEnhancer);
     
    export default store;

    src/App.js

    import React, { Component } from 'react'
    import './App.css';
    import About from './components/About'
    import Test from './components/Test'
    import { HashRouter as Router, Routes, Route, Link } from 'react-router-dom'
    
    class App extends Component {
      render() {
        return (
          <div className="App">
            <Router>
              <Link to={'/about'}>Go About Page</Link>&nbsp;&nbsp;|&nbsp;&nbsp;
              <Link to={'/test'}>Go Test Page</Link>
              <Routes>
                <Route path='/' element={<About />}></Route>
                <Route path='/about' element={<About />}></Route>
                <Route path='/test' element={<Test />}></Route>
              </Routes>
            </Router>
          </div>
        );
      }
    }
    
    export default App;

    src/index.js

    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import './index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    import { Provider } from 'react-redux';
    import store from './store'
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <Provider store={store}>
        <React.StrictMode>
          <App />
        </React.StrictMode>
      </Provider>
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    

    package.json

    {
      "name": "react-study",
      "version": "0.1.0",
      "private": true,
      "dependencies": {
        "@testing-library/jest-dom": "^5.16.5",
        "@testing-library/react": "^13.4.0",
        "@testing-library/user-event": "^13.5.0",
        "react": "^18.2.0",
        "react-dom": "^18.2.0",
        "react-redux": "^8.0.2",
        "react-router-dom": "^6.3.0",
        "react-scripts": "5.0.1",
        "redux": "^4.2.0",
        "redux-thunk": "^2.4.1",
        "web-vitals": "^2.1.4"
      },
      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      },
      "eslintConfig": {
        "extends": [
          "react-app",
          "react-app/jest"
        ]
      },
      "browserslist": {
        "production": [
          ">0.2%",
          "not dead",
          "not op_mini all"
        ],
        "development": [
          "last 1 chrome version",
          "last 1 firefox version",
          "last 1 safari version"
        ]
      }
    }

    原文链接:https://blog.csdn.net/lilygg/article/details/118256153


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!