react-redux、分模块modules、模拟登录退出流程

1,536 阅读5分钟

一、redux的介绍

  1. Redux是JavaScript状态容器, 提供可与策划的状态管理。相当于vue的vuex, 可以跨组件、跨页面推送数据。应用场景如:购物车、会员登录等功能模块。
  2. Redux由Flux演变而来, 避开了Flux的复杂性. 不管你有没有使用过他们, 只需几分钟就能上手Redux。
  3. Redux的基本思想是整个应用的state保持在一个单一的store中。store就是一个简单的Javascript对象,而改变应用state的唯一方式在应用中触发actions,然后为这些actions编写reducers来修改state。整个state转化是在reducers中完成,并且不应该有任何副作用。

二、redux的安装

npm install redux --save
npm install react-redux --save

三、 redux的使用案例

3.1 先构建一个redux的store

## 我们要建立一个Count计数组件, 在pages/index页面中引入改组件, 然后在项目的index.js入口文件中维护好redux的store关系
## 1. Count 组件
import React from 'react'

class Index extends React.Component {
    render() {
        return (
            <div>
                子组件计数器: 0
            </div>
        )
    }
}

export default Index

## 2. pages/index页面
import React from 'react';
import  Count from '../../components/Count'
import  {connect} from  'react-redux'
class Index extends React.Component {
    constructor() {
        super();
        this.state = {}
        this.num = 0;
    }

    // 1. 添加商品数量, 触发actions
    incCount() {
        this.props.dispatch({type: 'INC', data: {count: ++this.num}})
    }

    render() {
        return (
            <div>
                <Count />
                 计数器: <button type={'button'}>-</button> {this.props.state.count} <button type={'button'} onClick={this.incCount.bind(this)}>+</button>
            </div>
        )
    }
}

export default connect(state => {
    return {
        state: state
    }
})(Index);

3. index入口页面
import React from 'react';
import ReactDOM from 'react-dom';
import  {createStore} from 'redux'
import  {Provider} from 'react-redux'
import './assets/css/common/public.css';
import RouterComponent from './router';
import * as serviceWorker from './serviceWorker';

// 2. 商品装车
function counterReducer(state ={count: 10},action) {
    console.log(action)
    return state
}

// 3. 存入仓库
let store = createStore(counterReducer)

function App(){
    return (
        <React.Fragment>
            <Provider store={store}>
                <RouterComponent></RouterComponent>
            </Provider>
        </React.Fragment>
    )
}

ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();

备注: 上述代码主要是在入口文件中, 创建了reduce的store, 并把store通过Provider组件传递给下面的路由; 在pages/index页面中通过connent引入store, 同时本页面写了个相当于vuex中的dispatch,自增count的方法.这样就初始化好了redux的环境, 效果如下

2021-07-18-22-19-55.gif

3.2 初始化环境好了, 我们就开始通过redux实现count的自增了

只需要在入口的index未文中处理好dispatch传过来的值的逻辑就好了

// 2. 商品装车
function counterReducer(state ={count: 0},action) {
    console.log(action)
    if(action.data && action.data.count) {
        // 引用对象, 使用es6的assign浅拷贝, 实现数据的实时更新
        state = Object.assign({},state,action.data)
    }
    return state
}

2021-07-18-22-27-03.gif

3.3 接下来需要写递减方法, 在Count子组件中共享reducer中的count变量

## 1. 首先在pages/index 页面中写一个自减方法, 并绑定事件.
// 1.2. 减少商品数量, 触发actions
decCount() {
    this.props.dispatch({type: 'DEC', data: {count: --this.num}})
}
<button type={'button'} onClick={this.decCount.bind(this)}>

## 2. index入口文件中区分dispatch过来的INC, 和DEC事件
// 2. 商品装车
function counterReducer(state ={count: 0},action) {
    switch (action.type) {
        case 'INC':
            // 引用对象, 使用es6的assign浅拷贝, 实现数据的实时更新
            return Object.assign({},state,action.data)
            break;
        case 'DEC':
            // ES6中的浅拷贝
            return {...state,...action.data}
            break;
        default:
            return state
            break;
    }
}

## 3. 最后在Count组件中也通过connect形式去那ruduce中的count变量
import React from 'react'
import {connect} from 'react-redux'

class Index extends React.Component {
    render() {
        return (
            <div>
                子组件计数器: {this.props.state.count}
            </div>
        )
    }
}

// state => ({state}) es6 返回state
export default connect(state => ({state}))(Index)

效果如下:

2021-07-18-22-54-57.gif

3.4 如何在不同业务中使用redux呢? 使用combineReducers方法

// 1. 新建一个简单的会员登录页面
import React from 'react';
import {connect} from  'react-redux'

class Index extends React.Component {
    constructor(){
        super();
        this.state={
            username:"",
            password:""
        }
    }
    doLogin(){
        if(this.state.username.match(/^\s*$/)){
            alert("请输入用户名");
            return;
        }
        if(this.state.password.match(/^\s*$/)){
            alert("请输入密码");
            return
        }
        this.props.dispatch({type: 'LOGIN', data: {username: this.state.username, isLogin: true}})
        this.props.history.go(-1);
    }
    componentDidMount(){
        // console.log(this.props);
        if(this.props.location.state){
            console.log("从"+this.props.location.state.from.pathname+"页面跳转过来");
        }
    }
    render() {
        return (
            <div>
                用户名:<input type="text" placeholder="请输入用户名" onChange={(e)=>{this.setState({username:e.target.value})}}/><br/>
                密码:<input type="text" placeholder="请输入密码"  onChange={(e)=>{this.setState({password:e.target.value})}} /><br/>
                <button type="button" onClick={this.doLogin.bind(this)}>登录</button>
            </div>
        )
    }
}

export default connect()(Index);

// 2. 在index.js的入口文件中写好响应的reducer方法
// 默认的登录state
let defaultLoginState = {
    username: localStorage['username'] ? localStorage['username'] : '' ,
    isLogin: localStorage['isLogin'] ? Boolean(localStorage['isLogin']) : false
}

//  把登录的信息放入redux中
function loginReducer(state =defaultLoginState, action) {
    switch (action.type) {
        case 'LOGIN':
            console.log(action)
            // 刷新后reduce中的数据会丢失, 一般配合本地存储使用
            localStorage['username']=action.data.username;
            localStorage['isLogin']=action.data.isLogin;
            return {...state,...action.data}
        default:
            return state
    }
}

// 3. 使用combineReducers方法, 合并两个reducer方法, 相当于vuex中的分模块管理
let store = createStore(combineReducers({
    counter: counterReducer,
    login: loginReducer
}))

// 4. 修改之前使用的counter store
{this.props.state.count} 变成了 {this.props.state.counter.count} 

效果如下:

2021-07-19-23-04-38.gif

3.5 使用redux模拟登录退出业务流程

// 1. 写一个用户中心退出页面
import React from 'react';
import {connect} from 'react-redux'

class Index extends React.Component {
    constructor(props){
        super(props);
        this.state={};
    }
    componentDidMount(){

    }
    outLogin(){
        this.props.dispatch({type:'OUT_LOGIN'})
        this.props.history.replace("/login");
    }
    render() {
        return (
            <div>
                欢迎{this.props.state.user.username}回来!<br/>
                <button type="button" onClick={this.outLogin.bind(this)}>安全退出</button>
            </div>
        )
    }
}

export default connect(state => ({state}))(Index);

// 2. 在index.js入口文件中写退出的reducers方法
case 'OUT_LOGIN':
    localStorage.clear();
    return Object.assign({},state,{username: '',isLogin: false})
    
// 3. 从react官网中copy一个router-auth的js, 相当于vue中路由守卫
import React from 'react';
import {Route,Redirect} from 'react-router-dom';
export function AuthRoute({ component:Component, ...rest }) {
    return (
        <Route {...rest} render={props =>
            Boolean(localStorage['isLogin']) ? (
                <Component {...props} />
            ) : (
                <Redirect
                    to={{
                        pathname: "/login",
                        state: { from: props.location }
                    }}
                />
            )
        }
        />
    );
}

// 4. 把router-auth 加入到用户中心页面, 这样用户退出后, 再进用户中心页面就会重定向到登录页面
<AuthRoute path="/user" component={UserPage}></AuthRoute>

以上代码就把用户登录退出的业务流程写好了(当然实际中会去调Api):

2021-07-20-00-03-05.gif

四、使用redux modules 分模块管理数据

我们发现在index.js入口文件写很多redux的业务, js会特别的冗余, 这时就需要分模块封装。

  1. 构建store module目录

image.png

# 1. reducers中
index.js
import  {createStore,combineReducers} from 'redux'
import counterReducer from './count'
import loginReducer from './user'

// 3. 存入仓库
let store = createStore(combineReducers({
    counter: counterReducer,
    user: loginReducer
}))

export default store

user.js
// 默认的登录state
let defaultLoginState = {
    username: localStorage['username'] ? localStorage['username'] : '' ,
    isLogin: localStorage['isLogin'] ? Boolean(localStorage['isLogin']) : false
}

//  把登录的信息放入redux中
 function loginReducer(state =defaultLoginState, action) {
    switch (action.type) {
        case 'LOGIN':
            console.log(action)
            localStorage['username']=action.data.username;
            localStorage['isLogin']=action.data.isLogin;
            return {...state,...action.data}
        case 'OUT_LOGIN':
            localStorage.clear();
            return Object.assign({},state,{username: '',isLogin: false})
        default:
            return state
    }
}

export  default loginReducer

count.js
// 2. 商品装车
 function counterReducer(state ={count: 0},action) {
    switch (action.type) {
        case 'INC':
            // 引用对象, 使用es6的assign浅拷贝, 实现数据的实时更新
            return Object.assign({},state,action.data)
        case 'DEC':
            // ES6中的浅拷贝
            return {...state,...action.data}
        default:
            return state
    }
}

export default counterReducer


2. acitions 中
index.js
import * as counter from './count'
import * as user from './user'

export default  {
    counter,
    user
}

user.js
// 登录方法
export function login(data) {
    return {type: 'LOGIN', data}
}

// 退出方法
export function logout() {
    return {type:'OUT_LOGIN'}
}

count.js
// 自增方法
export function incCount(data) {
    return {type: 'INC', data: data}
}

// 自减方法
export function decCount(data) {
    return {type: 'DEC', data: data}
}

最后再改一下调用reducers和actions的地方就好了

index.js入口文件只需要导入store就好了, 不需要去处理具体业务
import store from './store/reducers/index'
<Provider store={store}>
    <RouterComponent></RouterComponent>
</Provider>


// 其他使用到actions的页面, 只需要传数据就好了, 不需要指定type是什么
计算页
// 1.1 添加商品数量, 触发actions
incCount() {
    // this.props.dispatch({type: 'INC', data: {count: ++this.num}})
    this.props.dispatch(actions.counter.incCount({count: ++this.num}))
}

// 1.2. 减少商品数量, 触发actions
decCount() {
    // this.props.dispatch({type: 'DEC', data: {count: --this.num}})
    this.props.dispatch(actions.counter.decCount({count: --this.num}))
}

登录页
// this.props.dispatch({type: 'LOGIN', data: {username: this.state.username, isLogin: true}})
this.props.dispatch(actions.user.login( {username: this.state.username, isLogin: true}))

用户中心页
// this.props.dispatch({type:'OUT_LOGIN'})
this.props.dispatch(actions.user.logout())

结果还是和上面图一样的, module改造成功 2021-07-20-00-03-05.gif