自学内容网 自学内容网

React学习05 - redux

redux工作流程

redux理解

redux是一个专门用于状态管理的JS库,可以用在react, angular, vue 等项目中。在与react配合使用时,集中式管理react应用中多个组件共享的状态。
当某个组件的状态需要让其他组件随时可以拿到时,当一个组件需要改变另一个组件的状态时,可以使用redux。
为什么不用消息订阅与发布?消息订阅与发布需要组件挂载后才能发布获取消息,当想要不挂载组件,先发布消息,再挂载组件接收消息的时候,就需要redux。
在react中使用redux,需要使用Redux Toolkit(RTK)和react-redux。

redux理解及三个核心概念

redux原理图

根据原理图,例如,当页面需要将一个数据+2,当前该数据为0 。

  1. Action Creators会创建一个action,把这个action分发(dispatch)给store,我们给action执行的操作自命名为Add,操作的数据为2。
  2. store接收到action后,将当前的状态0,和action一起交给Reducers,Reducers在原状态0的基础上,执行操作Add,也就是+2,然后返回新状态数据2 。
  3. Store接收到新状态,页面组件可以通过getState方法获取新状态数据。

当页面初次加载时,是在Reducers内初始化状态的,原状态为undefined,操作为初始化,Reducers可以初始化和加工状态。
三个核心概念:action, reducer, store。
action 包含两个属性,1.type 标识性属性,唯一且必要,值为字符串,2.data 数据属性,可选,值为任意类型。每个组件都可以有自己的ActionCreator。

{type:'自定义操作名称', data:数据} 

reducer是一个函数,根据旧的state和action产生新的state。每个组件都可以有自己的reducer。
store是一个对象,将state, action, reducer联系在一起。

import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)  // 获取store对象

通常集中状态管理的部分会在src下单独创建一个store目录,当建立很多子store模块时,在store目录下创建一个modules文件夹,包含所有子store模块文件,在store文件夹的index.js中将所有子模块组合并导出store。

redux核心api

通过案例代码理解store三个核心API: getState, dispatch, subscribe。在src下新建目录redux,创建store.js和demo_reducer.js两个文件,终端增加redux包。

npm add redux

redux4.0.5

// store.js
// 引入创建store的方法
import {createStore} from 'redux'
// 引入为组件服务的reducer
import demoReducer from './demo_reducer'
// 创建并暴露store
const store = createStore(demoReducer)
export default store

// demo_reducer.js  为Demo组件服务的reducer,本质是函数,返回新状态
const initState = 0; // 初始化状态
export default function demoReducer(preState=initState,action){
  const {type,data} = action;
  switch(key){
    case 'increment':
      return preState + data;
    case 'decrement':
      return preState - data;
    default:
      return 0; //无指定操作,即为初始化状态操作
  }
}

// Demo组件
import store from '../../redux/store'
export default class Demo extends Component{
  componentDisMount(){
    // 检测redux的状态更新,当前页面的状态随redux中的更新
    store.subscribe(()=>{
      this.setState({})
    })
  }
  add = ()=>{
    store.dispatch({type:'increment',data:2})
  }
  render(){
    return (
      <div>
        <h1>当前值:{store.getState()}</h1>
        <button onClick={this.add}>2</button>
      </div>
    )
  }
}

// App index.js Demo组件的subscribe可以去掉,放到App里,避免多个组件需要订阅重复代码
store.subscribe(()=>{
  ReactDOM.render(<App/>,document.getElementById('root'))
})

以上就完成了一个精简的redux。下面增加ActionCreator,将redux完善。在redux目录下创建demo_action.js。对应修改组件里调用dispatch。

// demo_action.js
export default function createIncrementAction(data){
  return {type:'increment',data}
}

// Demo组件
// 引入actionCreator,专门用于创建action对象
import createIncrementAction from '../../redux/demo_action'
// add方法改为
add = ()=>{
  store.dispatch(createIncrementAction(2))
}

然后在redux目录下新建constant.js,用来将操作名集中管理,避免写错字符串。对应的修改reducer和action里的操作名。

// constant.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';

// demo_reducer.js
import {INCREMENT,DECREMENT} from './constant.js'
switch(key){
    case INCREMENT:
      return preState + data;
    case DECREMENT:
      return preState - data;
    default:
      return 0; 
  }

// demo_action.js
import {INCREMENT,DECREMENT} from './constant.js'
export default function createIncrementAction(data){
  return {type:INCREMENT,data}
}

以上就是完整的redux,有action,store,reducer,组件和constant五个文件内容,其中组件、store、reducer是必须的。

redux异步编程

action一般为一个对象,action也可以为一个函数,当它为对象时,是同步的,当它是函数时,是异步的。异步action应该包含ajax,计时器等异步任务,对象和数组满足不了,所以异步的action要使用函数实现。

// Demo组件
import store from '../../redux/store'
export default class Demo extends Component{
  componentDisMount(){
    // 检测redux的状态更新,当前页面的状态随redux中的更新
    store.subscribe(()=>{
      this.setState({})
    })
  }
  asyncAdd = ()=>{
    setTimeout(()=>{
      store.dispatch(createIncrementAction(2))
    },500) // 等待500毫秒后再执行加2,等待动作在组件里,组件等待500毫秒后发送请求去让redux执行一系列操作
  }
  render(){
    return (
      <div>
        <h1>当前值:{store.getState()}</h1>
        <button onClick={this.asyncAdd}>异步加2</button>
      </div>
    )
  }
}

// demo_action.js
import {INCREMENT,DECREMENT} from './constant.js'
export default function createIncrementAction(data){
  return {type:INCREMENT,data}
}

以下尝试实现一个函数类型的action,理想store接收到函数类型的action,丢给reducer处理,等待500毫秒后再通过store.dispatch去生成一个对象类型的action,然后reducer执行+2操作。实际会报错,action传递到store处理必须要是对象类型的。

// 修改组件内异步加2的方法
  asyncAdd = ()=>{
      store.dispatch(createAsyncIncrementAction(2,500)); //组件立即发送请求,让redux去等待500毫秒后再执行一系列操作
  }

// 在action文件中增加createAsyncIncrementAction方法,实现异步action
export const createAsyncIncrementAction = (data,time)=>{
  return ()=>{
    setTimout(()=>{
      store.dispatch(createIncrementAction(data))
    },time)
  } // return的是一个函数类型的action,里面包含异步任务。store接收到函数类型的action,丢给reducer处理不了函数类型的,所以返回报错
}

引入中间件,当返回的action是一个函数包含异步任务时,由中间件处理异步部分,将异步任务完成后生成的对象类型的action传递给store,再去真正操作数据,解决报错。

npm add redux-thunk
// store.js
// 引入创建store的方法
import {createStore,applyMiddleware} from 'redux'
// 引入为组件服务的reducer
import demoReducer from './demo_reducer'
// 引入redux-thunk
import thunk from 'redux-thunk'
// 创建并暴露store
const store = createStore(demoReducer,applyMiddleware(thunk))
export default store

// 在action文件中增加createAsyncIncrementAction方法,实现异步action
export const createAsyncIncrementAction = (data,time)=>{
  return ()=>{
    setTimout((dispatch)=>{
      dispatch(createIncrementAction(data))
    },time)
  } // return的是一个函数类型的action,里面包含异步任务。store接收到函数类型的action,传递给中间件处理,此时在处理的依旧是store,所以可以传递dispatch函数,免去store.dispatch调用
}

react-redux

react-redux用来连接redux和react组件。
所有的UI组件都应该包含一个在一个容器组件里,是父子关系。
容器组件才能使用redux的API与redux打交道,UI组件不能使用任何redux的API。
容器组件会通过props传给UI组件redux中保存的状态和用于操作状态的方法。
react-redux模型图
UI组件内不能含有redux,创建容器组件,在容器组件内引入react-redux

npm add react-redux

根据模型图,容器组件要向外连接redux,向内连接UI组件。容器组件通过react-redux连接UI组件,通过store连接redux。

// 容器组件DemoContainer/index.jsx
// 1.引入UI组件
import DemoUI from '../../components/Demo'
// 2.引入react-redux, connect用于连接UI组件和redux
import {connect} from 'react-redux'
// 3.连接UI组件和容器组件
const DemoContainer = connect()(DemoUI); // connect是一个函数,返回值依然是一个函数
export default DemoContainer; // 向外暴露一个容器组件

// 在App内
// 1.引入容器组件,不引入UI组件
import DemoContainer from './containers/DemoContainer'
// 2.引入store
import store from '../../redux/store'
export default class App extends Component{
  render(){
    return (
      <div>
        <DemoContainer store={store}/>
      </div>
    ) // 3.连接容器组件和redux,给容器组件传递store
  }
}

容器组件向UI组件通过props传值,通过connect函数的第一个参数,connect第一个参数为函数,返回的值作为UI组件的状态。connect第二个参数为函数,返回值作为UI组件操作状态的方法。

// mapStateToProps用于传递状态,返回一个对象,key和value对应props的key value
function mapStateToProps(state){
  // return {n:900}; // 相当于<DemoUI n={900} />
  return {n:state}
}
// mapDispatchToProps用于传递操作状态的方法,返回一个对象,key和value对应props的key value
function mapDispatchToProps(dispatch){
  return {add: (data)=>{
   dispatch(createIncrementAction(data)) // 通知redux做加法
  }} 
}
export default connect(mapStateToProps,mapDispatchToProps)(DemoUI) 

容器组件的store是靠props传进去的,而不是引入的。

mapDispatchToProps简化

// ui组件
increment = ()=>{
  const value = this.selectNumber;
  this.props.jia(value)
}

// 容器组件mapDispatchToProps
const mapDispatchToProps = dispatch=>({
  jia:number => dispatch(createIncrementAction(number)),
  jian: number => dispatch(createDecrementAction(number))
})
// 继续简化,将mapStateToProps和mapDispatchToProps省略,直接使用connect函数
export default connect(
 state => ({n:state}), //mapStateToProps简写
 {
   jia:createIncrementAction,
   jian:createDecrementAction
 } // mapDispatchToProps简写
)(DemoUI) 

容器组件使用connect连接ui组件和redux,容器组件可以自调用subscribe更新页面,所以可以省略store.subscribe的调用。
可以在App index.js内给所有子组件传递store,在子组件内可以不引入store直接使用

// App index.js
import store from './redux/store'
import {Provider} from 'reatc-redux'
ReactDOM.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.getElementById('root')
)

UI组件可以写在容器组件的文件内,合并为一个文件,只暴露容器组件。
总结,组件与redux打交道:

  1. 定义UI组件,不暴露
  2. 使用connect生成容器组件,并暴露
    connect(
    state => ({key:value}), // 映射状态
    {key:xxxAction} // 映射操作状态的方法
    )(UI组件)
  3. 在UI组件内通过this.props.xxx读取和操作状态
// UI组件和容器组件合并
class Demo extends Component{
  add = ()=>{
    this.props.jiafa(1)
  }
  render(){
    return (
      <div>
        <h1>和为:{this.props.sum}</h1>
        <button onClick={this.add}>点击+1</button>
      </div>
    )
  }
}

export default connect(
  state => ({sum:state}), 
  {jiafa:createIncrementAction} 
)(Demo)

组件间数据共享

创建Person组件,创建对应的action, reducer,在store内使用combineReducers把所有组件的reducer合并。

// store.js
import demoRudecer from './reducers/demo'
import personReducer from './reducers/person'
import {combineReducers} from 'redux'
const allReducer = combineReducers({
  sum:demoReducer,
  persons:personReducer
}) // combineReducers里传递的对象就是store管理的状态对象

// Demo组件里connect使用
state => ({s: state.sum, num: state.persons.length}) // 传递给UI组件使用

纯函数

纯函数只要是同样的输入(实参),必定得到同样的输出(返回)。
纯函数内不能改写参数数据,不能产生副作用(比如不能发送网络请求、不能连接输入输出设备等有可能造成数据丢失的行为),不能调用Date.now或Math.random等返回值不定的方法。
redux的reducer函数必须是一个纯函数。

redux调试工具

引入工具库,可以在浏览器看到redux内的状态

npm add redux-devtools-extension
// store.js
// 引入工具库
import {composeWithDevTools} from 'redux-devtools-extention'
export default createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

项目打包

执行命令生成build文件夹

npm run build

将生成的build文件夹放到服务器,使用Node或Java搭建服务器,或者使用serve库。

npm i serve -g // 全局安装库
serve a  // 以a文件夹为根目录作为服务器运行
serve // 以当前文件为服务器运行

原文地址:https://blog.csdn.net/Meowmow/article/details/143100893

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!