React学习笔录三 Redux-saga UmiJs dva

1. redux-saga  官方文档地址

// redux-thunk 和 redux-saga

redux-thunk的action要返回函数,这本身已经破坏了react的action的初衷(是个对象)

redux-saga的action还是对象,符合react的要求

redux-saga 对于副作用(数据获取,浏览器缓存获取),易于管理,执行,测试和失败处理,对复杂逻辑能够很好的支持,但是redux-thunk就不能完美支持
// store/index.js (redux的注册地方,使用了中间件redux-saga)

import { createStore, applyMiddleware, combineReducers } from "redux";

import createSaga from "redux-saga";

import logger from "redux-logger";

import { countReducer } from "./counter";

import { userReducer } from "./user";

import rootSaga from './sagas'  //saga异步的业务js(根级)

const saga = createSaga();

const store = createStore(
  combineReducers({ count: countReducer, user: userReducer }),
  applyMiddleware(logger, saga)
);

saga.run(rootSaga); //执行监听

export default store;
// store/sagas.js 
// saga具体的api参考官方
// saga的action形式跟redux一样,当redux触发action时候,saga会进行拦截处理,执行监听对应的异步处理,然后通过put继续派发

import { call, put, all, takeEvery } from "redux-saga/effects";

import { toLogin } from "../api"; //这是模拟接口的api

import { REQUEST_LOGIN, LOGINSUCCESS, LOGINFAIL, TOLOGIN } from "./types";

// worker saga  异步的执行函数

function* login(action) {
  try {
    yield put({ type: REQUEST_LOGIN }); //请求登录
    const result = yield call(toLogin, action.name); //接口登录
    yield put({ type: LOGINSUCCESS, result }); //登录成功
  } catch (message) {
    yield put({ type: LOGINFAIL, message }); //登录失败
  }
}

// 监听action(TOLOGIN),一旦触发这个action,就执行login

function* watchLogin() {
  yield takeEvery(TOLOGIN, login);
}

// 多个监听时候,进行合并监听

export default function* rootSaga() {
  yield all([watchLogin()]);
}
// api.js 模拟登录,成功返回和数据

export const toLogin = name => {
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve({ name });
      // reject('用户名输入不正确')
    }, 2000);
  });
};
// store/user.js
// 相比较redux-thunk,action(login)有多改变,返回的不是一个函数,而是个对象

import { REQUEST_LOGIN, LOGINSUCCESS, TOLOGIN, LOGINFAIL } from "./types";

export const userReducer = (state = { isLogin: false, loading: false, error:'' }, action) => {
  switch (action.type) {
    // 登录中
    case REQUEST_LOGIN:
      return {
          isLogin: false,
          loading: true,
          error: ''
      };
    // 登录成功
    case LOGINSUCCESS:
      return {
        isLogin: true,
        loading: false,
        error: '',
        data: action.result
    };
    // 登录失败
    case LOGINFAIL:
      return {
        isLogin: false,
        loading: false,
        error: action.message
    };
    default:
      return state;
  }
};

// action
// export const login=()=>(dispatch)=>{
//     dispatch({type: REQUEST_LOGIN});
//     // 异步登录操作模拟
//     setTimeout(() => {
//         dispatch({type: LOGIN});
//     }, 2000);
// }

export const login=(name)=>({type: TOLOGIN,name})
// Login.js  使用的组件

import React, { Component } from "react";
import { connect } from "react-redux";
import { login } from "./store/user";
import { Redirect } from "react-router-dom";

// 包含在空间外层的,form的直接元素,控制布局和样式之类的,错误消息提示
function FormItem(props) {
  const { children, status, help } = props;
  return (
    <div>
      {children}
      {status === "error" && (
        <p style={{ color: "red", fontSize: "12px" }}>{help}</p>
      )}
    </div>
  );
}

// 高阶组件 扩展传入组件功能,返回新的组件
// 增加字段的验证,所有字段的全局验证,表单组件的装饰器[值和name由父级控制]
function MyFormCreate(Com) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = {};
      this.options = {};
    }

    // 输入框的change事件

    handleChange = e => {
      const { value, name } = e.target;
 

      this.setState(
        {
          [name]: value
        },
        () => {
          this.validateField(name); //用回调方式,确保值变了,进行验证
        }
      );
    };

    // 全局验证,扩展出去,给使用组件调用
    validateAllField = cb => {
      const result = Object.keys(this.options).map(name => {
        return this.validateField(name);
      });

      cb(result.every(bool => !!bool), this.state);
    };

    // 单个字段验证
    validateField = name => {
      const rules = this.options[name].rules;
      const isValid = !rules.some(rule => {
        if (rule.required) {
          if (!this.state[name]) {
            this.setState({
              [name + "Message"]: rule.message
            });
            return true;
          }
        }
        return false;
      });
      if (isValid) {
        this.setState({
          [name + "Message"]: ""
        });
      }
      return isValid;
    };

    // 获取错误信息 给FormItem使用
    getErrorMsg = name => {
      return this.state[name + "Message"];
    };

    // 包装表单控件,value和name由父级控制
    getFieldDecorator = (name, option, Com) => {
      this.options[name] = option;
      return (
        <div>
          {React.cloneElement(Com, {
            name: name,
            value: this.state[name] || "",
            onChange: this.handleChange
          })}
        </div>
      );
    };

    render() {
      return (
        <Com
          {...this.props}
          getFieldDecorator={this.getFieldDecorator}
          validateAllField={this.validateAllField}
          getErrorMsg={this.getErrorMsg}
        />
      );
    }
  };
}

// 装饰器组件 高阶组件的包装
// 使用组件
@connect(
  state => ({ ...state.user }),  //这里集体获取用户数据
  { login }
)
@MyFormCreate
class MyForm extends Component {
  // 提交
  submitForm = () => {
    this.props.validateAllField((isValid, data) => {
      if (isValid) {
        console.log("登录:", data);
        console.log(this.props)
        this.props.login(data.userName); // 这里登录派发action,传参userName
      } else {
        console.log("not valid");
      }
    });
  };

  render() {
    const { getFieldDecorator, getErrorMsg, isLogin, location, loading, error, data } = this.props;
    if (isLogin) {
      return <Redirect to={location.state.redirect || "/"} />;
    }
    return (
      <div>
        {/*这里展示数据和错误*/}
        {error && <p style={{color:'red',fontSize:'12px'}}>登录错误:{error}</p>}
        {data && <p>登录的姓名: {data.name}</p>}
        <FormItem
          status={getErrorMsg("userName") ? "error" : ""}
          help={getErrorMsg("userName")}
        >
          {getFieldDecorator(
            "userName",
            {
              rules: [{ required: true, message: "用户名不能为空" }]
            },
            <input type="text" />
          )}
        </FormItem>

        <FormItem
          status={getErrorMsg("password") ? "error" : ""}
          help={getErrorMsg("password")}
        >
          {getFieldDecorator(
            "password",
            {
              rules: [{ required: true, message: "密码不能为空" }]
            },
            <input type="password" />
          )}
        </FormItem>

        <button onClick={this.submitForm} disabled={loading}>{loading?'登录中...':'登录'}</button>
      </div>
    );
  }
}

export default MyForm;

//    下面这个基本看不到,直接就跳转到页面了 

 

2. 前端React开箱即用框架  UmiJS  (内部涵盖了React router和 Dva[基于redux和redux-saga的数据流解决方案]等) 

 目录:

// config/config.js


export default {
  plugins: [
    [
      "umi-plugin-react",
      {
        dva: true, //启用dva
        antd: true  //启用antd,要安装antd
      }
    ]
  ],
  routes: [
    {
      path: "/login",
      component: "./login/index"  //根目录是pages下
    },
    {
      path: "/about",
      component: "./about/index",
      Routes: ["./routes/PrivateRoute.js"] //这个根目录是最外层,和src,config同级
    },
    {
      path: "/",
      component: "./index"
    },
    {
      path: "/users",
      component: "./users/_layout", //嵌套路由
      Routes: ["./routes/PrivateRoute.js"],
      routes: [
        {
          path: "/users",
          component: "./users/index"
        },
        {
          path: "/users/:id",
          component: "./users/$id"
        },
        {
          component: "./404"  //嵌套的路由,404要重新定义,要不然就会用默认的404
        }
      ]
    },
    {
      component: "./404"  // 自定义的404,根目录是pages下
    }
  ]
};
// mock数据
// mock/user.js

export default {
  "post /api/login": function(req, res) {
    setTimeout(() => {
      res.json({ login: true });
    }, 2000);
  }
};
// src/pages/about/index.js


import styles from './index.css';

export default function() {
  return (
    <div className={styles.normal}>
      <h1>这是个About的页面</h1>
    </div>
  );
}


// src/pages/index.js


import styles from "./index.css";
import Link from "umi/link"; //声明式
import router from "umi/router"; //命令式

export default function() {
  return (
    <div className={styles.normal}>
      <ul>
        <li>
          <Link to={{ pathname: "/users" }}>用户信息总页</Link>
        </li>
        <li>
          <Link to={{ pathname: "/about" }}>About</Link>
        </li>
        <li
          style={{
            textDecoration: "underline",
            color: "blue",
            cursor: "pointer"
          }}
          onClick={() => router.push({ pathname: "/users/zs" })}
        >
          用户信息详情页,带参数
        </li>
      </ul>
    </div>
  );
}
// src/pages/login/index.js

import styles from "./index.css";
import { Button } from "antd";
import { connect } from "dva"; // 获取react-redux的connect,这里封装在dva
import Redirect from "umi/redirect"; //跳转

import React, { Component } from "react";

//将redux数据转化成属性
@connect(
  state => ({
    isLogin: state.user.isLogin, //是否登录
    error: state.user.error, //错误
    loading: state.loading //这里用的是dva自带的loading
  }),
  { login: () => ({ type: "user/login" }) }  //登录的action
)
export class index extends Component {
  render() {
    const { login, isLogin, location, error, loading } = this.props;
    {/*登录了就直接跳转,跳转的路由从权限路由高阶组件那边传过来*/}
    if (isLogin) return <Redirect to={location.state.redirect} />;
    return (
      <div className={styles.normal}>
        <h1>登录页</h1>
        {error && <p style={{ color: "red" }}>错误信息: {error}</p>}
        <p>
          {/*loading会带有命名空间*/}
          <Button disabled={loading.models.user} onClick={login}>
            {loading.models.user ? "登录中..." : "登录"}
          </Button>
        </p>
      </div>
    );
  }
}

export default index;
// src/pages/404.js

import React, { Component } from 'react'

export class NotFound extends Component {
    render() {
        return (
            <div>
                页面找不到了,可惜
            </div>
        )
    }
}

export default NotFound
// src/pages/users/_layout.js


import styles from './_layout.css';

export default function(props) {
  return (
    <div className={styles.normal}>
      <h3>这是个嵌套路由,使用约定的_layout,要加上props.children才会有子路由 </h3>
      {props.children}
    </div>
  );
}
// src/pages/users/$id.js

import React, { Component } from "react";

export class $id extends Component {
  render() {
    const { match } = this.props;
    return (
    <div>当前的路由参数id:{match.params.id}</div>
    );
  }
}

export default $id;
// src/pages/users/index.js


import styles from './index.css';

export default function() {
  return (
    <div className={styles.normal}>
      <h1>Page index</h1>
    </div>
  );
}
// src/utils/api.js

import axios from 'axios'
export const toLogin=()=>{
    return axios.post('/api/login');
    // return Promise.reject("网络异常")
}
// src/models/user.js

import { toLogin } from "../../utils/api";

export default {
  namespace: "user",
  state: { isLogin: false, loading: false, error: "", data: "" }, //初始数据state
  reducers: {
    /**
     * 请求登录action
     * @param {*} state 
     * @param {*} action 
     */
    loginRequest(state, action) {
      return {
        ...state,
        isLogin: false,
        loading: true,
        error: ""
      };
    },
    /**
     * 登录成功action
     * @param {*} state 
     * @param {*} action 
     */
    loginSuccess(state, action) {
      return {
        ...state,
        isLogin: true,
        loading: false,
        error: "",
        data: action.res
      };
    },
    /**
     * 登录失败action
     * @param {*} state 
     * @param {*} action 
     */
    loginFail(state, action) {
      return {
        ...state,
        isLogin: false,
        loading: true,
        error: action.message
      };
    }
  },
  effects: {
    /**
     * 登录的saga worder
     * @param {*} action 
     * @param {*} param1 
     */
    *login(action, { call, put }) {
      try {
        yield put({ type: "loginRequest" });
        const res = yield call(toLogin);
        yield put({ type: "loginSuccess", res });
      } catch (message) {
        yield put({ type: "loginFail", message });
      }
    }
  }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值