React第六篇 Redux

1,013 阅读5分钟

React 学习笔记

完整代码戳git

第一部分,腾讯课堂react入门课程
课程地址
React第一篇 npm、yarn介绍
React第二篇 React语法
React第三篇 单页应用开发
React第四篇 路由&Hooks
React第五篇 综合应用(登录+列表)

第二部分,Redux课程
课程地址
React第六篇 Redux
React第七篇 React Redux


redux工作流

安装

  • redux安装:npm install redux --save
  • 调试工具Redux DevTools安装:chrome应用商店下载

1. 基础入门

  1. 在src下新建reduxdemo.js
  2. 在src下新建reduxdemo/store文件夹存放数据存储代码
  3. reduxdemo/store下先编写index.js
  4. reduxdemo/store下新建reducer.js存放数据
  5. 通过按钮操作观察UI与数据的变化情况

src/reduxdemo.js

import React from 'react'
import 'antd/dist/antd.css'
import {Input,Button,List} from "antd";
import './redux.scss'
import store from './reduxdemo/store/index'

/**
 * @Description: redux基础应用
 * @author dongshuhuan
 * @date 2020-05-18
 */

export default class ToDoList extends React.Component{

    constructor(props){
        super(props)
        // console.log(store.getState())
        //1.通过store(reducer)给state赋值
        this.state = store.getState();
        //4.订阅 并改变组件状态 注意 store<=>组件 数据变化
        store.subscribe(this.storeChange.bind(this))
    }

    storeChange(){
        this.setState(store.getState())
    }

    //2. 处理输入事件
    handleInputChange = (event) =>{
        let val = event.target.value;
        console.log(val)
        const action = {
            type:'inputChange',
            value:val
        }
        //3. 接收到了input值,传递给了reducer
        store.dispatch(action)
    }

    // 5.dispatch处理按钮事件
    handleAdd = () =>{
        const action = {type:'addItem'}
        store.dispatch(action)
    }

    //6. 删除事件 带参传入action
    deleteItem (index){
        const action = {type:'deleteItem',index}
        store.dispatch(action)
    }

    render() {
        return <div className={'container'}>
            <div>
                <Input placeholder={this.state.placeHolder}
                       style={{width:'250px'}}
                       onChange={this.handleInputChange}
                       value={this.state.inputValue}/>
               <Button className={'button'} type={"primary"} onClick={this.handleAdd}>添加工作项</Button>
            </div>
            <div className={'list'}>
                <List bordered
                dataSource={this.state.list}
                      renderItem={(item,index)=>(
                          <List.Item onClick={this.deleteItem.bind(this,index)}>{item}</List.Item>
                      )}/>
            </div>
        </div>
    }
}

reduxdemo/store/index.js

/**
 * @Description: store仓库创建
 * @author dongshuhuan
 * @date 2020-05-18
 */

import {createStore} from 'redux'
import reducer from './reducer'

//reducer 注入
//window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()  chrome配置redux插件拓展
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
export default store

reduxdemo/store/reducer.js

/**
 * @Description:
 * @author dongshuhuan
 * @date 2020-05-18
 */

const defaultState = {
    inputValue:'',
    placeHolder:'输入内容',
    list : [
        '早九点开晨会,分配任务',
        '中午12点吃饭',
        '中午12点半,睡觉',
        '中午1点半,起床下午工作'
    ]

}
export default (state=defaultState,action)=>{
    console.log(state,action)
    //Reducer里只能接收state,不能改变state
    // 4.处理action 返回新的state
    if (action.type==='inputChange') {
        let newState = JSON.parse(JSON.stringify(state))
        newState.inputValue = action.value
        return newState
    }
    if (action.type==='addItem'){
        let newState = JSON.parse(JSON.stringify(state))
        newState.list.push(newState.inputValue)
        newState.inputValue=''
        return newState
    }
    if (action.type==='deleteItem'){
        let newState = JSON.parse(JSON.stringify(state))
        newState.list.splice(action.index,1)
        return newState
    }
    return state
}

2.代码优化

面向企业级开发的代码优化

2.1 action_types 将action类型进行封装

  • reduxdemo/store下新建action-types.js定义操作
  • 在reducer.js和reduxdemo.js中使用action-types中的actions
  • 这样做的好处时有效预防拼写错误 action-types.js
export const INPUTCHANGE = 'inputChange'
export const ADD_ITEM = 'addItem'
export const DELETE_ITEM = 'deleteItem'

reducer.js中修改

import {INPUTCHANGE,ADD_ITEM,DELETE_ITEM} from './action-types'

...

if (action.type==='inputChange') -> (action.type===INPUTCHANGE)

reduxdemo.js中修改

import {INPUTCHANGE,ADD_ITEM,DELETE_ITEM} from './action-types'

...

handleAdd = () =>{
        const action = {type:'addItem'}
        store.dispatch(action)
    }
    
    //修改为如下代码↓ ↓ ↓
    
handleAdd = () =>{
        const action = {type:ADD_ITEM}
        store.dispatch(action)
    }

2.2 actionCreator 将reduxdemo.js中的action操作进行封装

  • reduxdemo/store下新建action-creators.js封装操作
  • reduxdemo.js中使用action-creators中封装的方法

action-creators.js

import {INPUTCHANGE,ADD_ITEM,DELETE_ITEM} from './action-types'

export const changeInputAction = (val) =>({
    type:INPUTCHANGE,
    value:val
})

export const addItemAction = () =>({
    type:ADD_ITEM
})

export const deleteItemAction = (index) =>({
    type:DELETE_ITEM,
    index
})

reduxdemo.js中修改

import {changeInputAction} from './reduxdemo/store/action-creators'

...

    //2. 处理输入事件
    handleInputChange = (event) =>{
        let val = event.target.value;
         const action = {
             type:INPUTCHANGE,
             value:val
         }
        store.dispatch(action)
        
        //修改为如下代码↓ ↓ ↓
        
        //7 action-creators封装action操作
        const action = changeInputAction(val)
        store.dispatch(action)
    }
    ...

2.3 分离UI

  • 新建reduxdemoui.js ,只编写UI代码,将reduxdemo中的UI代码拷贝过来
  • 修改reduxdemo 将UI部分替换为reduxdemoui中的TodoListUI组件
  • reduxdemo中为TodoListUI传递属性,同时TodoListUI接收参数修改为props
  • 用无状态组件方式实现,将reduxdemoui.js修改为无状态组件
    reduxdemoui.js
// export default class ToDoListUI extends React.Component{
//
//     render() {
//         return (
//             <div className={'container'}>
//                 <div>
//                     <Input placeholder={this.props.placeHolder}
//                            style={{width:'250px'}}
//                            onChange={this.props.handleInputChange}
//                            value={this.props.inputValue}/>
//                     <Button className={'button'} type={"primary"} onClick={this.props.handleAdd}>添加工作项</Button>
//                 </div>
//                 <div className={'list'}>
//                     <List bordered
//                           dataSource={this.props.list}
//                           renderItem={(item,index)=>(
//                               <List.Item onClick={()=>this.props.deleteItem(index)}>{item}</List.Item>
//                           )}/>
//                 </div>
//             </div>
//         )
//     }
//
// }

//无状态组件
export default function TodoListUI(props) {
    return <div className={'container'}>
        <div>
            <Input placeholder={props.placeHolder}
                   style={{width:'250px'}}
                   onChange={props.handleInputChange}
                   value={props.inputValue}/>
            <Button className={'button'} type={"primary"} onClick={props.handleAdd}>添加工作项</Button>
        </div>
        <div className={'list'}>
            <List bordered
                  dataSource={props.list}
                  renderItem={(item,index)=>(
                      <List.Item onClick={()=>props.deleteItem(index)}>{item}</List.Item>
                  )}/>
        </div>
    </div>
}

reduxdemo.js

render() {
        // 8 封装UI代码
        return <ToDoListUI inputValue = {this.state.inputValue}
                           handleInputChange = {this.handleInputChange}
                           placeHolder = {this.state.placeholder}
                           list = {this.state.list}
                           handleAdd = {this.handleAdd}
                           deleteItem = {this.deleteItem}/>

    }

2.4 axios异步请求与redux的结合

reduxdemo.js进行网络请求

//9 axios异步请求与redux的结合
     componentDidMount() {
         //接口不可用,仅作为演示使用
         // const url = 'https://www.easy-mock.com/mock/5cfcce489dc7c36bd6da2c99/xiaojiejie/getList';
         axios.get('todolist.json')
             .then((res)=>{
                 console.log(res.data.data)
                 if (res.data.code===0){
                     const data = res.data.data;
                     const action = getListAction(data);
                     store.dispatch(action)
                     // console.log(data)
                 }
         })
     }

action-types.js

export const GET_LIST = 'getList'

action-creators.js

export const getListAction = (data) =>({
    type:GET_LIST,
    data
})

reducer.js

if (action.type===GET_LIST){
        console.log('网络请求')
        let newState = JSON.parse(JSON.stringify(state))
        const data = action.data
        newState.list = [...newState.list,...data]
        return newState
    }

效果图

3.redux 中间件

3.1 Redux-thunk

  • 安装 npm install --save redux-thunk 或 yarn add redux-thunk --save
  • store/index.js中导入redux-thunk
  • action-types.js中增加数据请求函数
  • reduxdemo.js导入网络请求函数并使用它

store/index.js

//thunk 中间件使用
//增强函数
//createStore只能传递两个参数,为保证chrome浏览器redux插件正常使用,需要使用增强函数
const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));
const store = createStore(
    reducer,
    enhancer)
export default store

action-types.js

// react-thunk 中间件方式
export const getTodoList = () =>{
  return(dispacth)=>{
      axios.get('todolist.json')
          .then((res)=>{
              console.log(res.data)
              if (res.data.code===0){
                  const data = res.data.data;
                  const action = getListAction(data);
                  dispacth(action)
              }

          })
  }
};

reduxdemo.js

    //9 axios异步请求与redux的结合
    componentDidMount() {
        //10.action-creators中 中间件进行异步请求网络
        const action = getTodoList();
        store.dispatch(action)
    }

3.2 redux-saga

  • 安装 npm install --save redux-saga 或 yarn add redux-saga --save
  • store/index.js中引入sega
  • action-types.js和action-creators.js添加代码
  • 编写sagas.js
  • reactdemo.js中导入和使用

store/index.js

import createSagaMiddleware from 'redux-saga'

//redux-sega
const segaMiddleware=createSagaMiddleware();
const composeEnhancers =   window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}):compose;
const enhancer = composeEnhancers(applyMiddleware(segaMiddleware));
const store = createStore(
    reducer,
    enhancer)
segaMiddleware.run(mySagas)
export default store

action-types.js

export const GET_MY_LIST = 'getMyList'

action-creators.js

//redux-saga 中间件方式请求数据
export const getMyListAction =()=>({
    type:GET_MY_LIST
});

sagas.js

import {takeEvery,put} from 'redux-saga/effects'
import {GET_MY_LIST} from './action-types'
import axios from "axios";
import {getListAction} from "./action-creators";

//generator函数
function* mySaga() {
    yield takeEvery(GET_MY_LIST,getList)
}

function* getList() {
    const res = yield axios.get('todolist.json');
    const action = getListAction(res.data.data)
    yield put(action)
    
}

export default mySaga

reduxdemo.js

    //9 axios异步请求与redux的结合
    componentDidMount() {
        //11. redux-saga中间件处理网络请求
        const action = getMyListAction();
        store.dispatch(action)
    }