欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

react-umi_dva项目

程序员文章站 2024-03-24 14:30:10
...

29.9React课程

第11节:umi_Dva路由及项目实现

(第11节:umi-Dva路由及项目实现&)

第11节:umi-Dva路由及项目实现&

项目创建

react-umi_dva项目

react-umi_dva项目

创建页面

react-umi_dva项目

创建目录结构

react-umi_dva项目

react-umi_dva项目

 router.js

import React from 'react';
import { Router as DefaultRouter, Route, Switch } from 'react-router-dom';
import dynamic from 'umi/dynamic';
import renderRoutes from 'umi/lib/renderRoutes';
import history from '@tmp/history';
import { routerRedux } from 'dva';

const Router = routerRedux.ConnectedRouter;

const routes = [
  {
    path: '/',
    component: require('../../layouts/index.js').default,
    routes: [
      {
        path: '/404',
        exact: true,
        component: require('../404.js').default,
      },
      {
        path: '/about',
        exact: true,
        component: require('../about.js').default,
        title: 'about page',
        Routes: [
          require('../../routes/PrivateRoute.js').default,
          require('../../routes/Test.js').default,
        ],
      },
      {
        path: '/goods',
        exact: true,
        component: require('../goods/index.js').default,
      },
      {
        path: '/goods',
        exact: true,
        component: require('../goods.js').default,
      },
      {
        path: '/',
        exact: true,
        component: require('../index.js').default,
      },
      {
        path: '/login',
        exact: true,
        component: require('../login.js').default,
      },
      {
        path: '/users',
        exact: false,
        component: require('../users/_layout.js').default,
        routes: [
          {
            path: '/users',
            exact: true,
            component: require('../users/index.js').default,
          },
          {
            path: '/users/:id',
            exact: true,
            component: require('../users/$id.js').default,
          },
          {
            component: () =>
              React.createElement(
                require('/Users/kele/.config/yarn/global/node_modules/umi-build-dev/lib/plugins/404/NotFound.js')
                  .default,
                { pagesPath: 'pages', hasRoutesInConfig: false },
              ),
          },
        ],
      },
      {
        path: '/:post/commit',
        exact: true,
        component: require('../$post/commit.js').default,
      },
      {
        path: '/:post',
        exact: true,
        component: require('../$post/index.js').default,
      },
      {
        component: () =>
          React.createElement(
            require('/Users/kele/.config/yarn/global/node_modules/umi-build-dev/lib/plugins/404/NotFound.js')
              .default,
            { pagesPath: 'pages', hasRoutesInConfig: false },
          ),
      },
    ],
  },
  {
    component: () =>
      React.createElement(
        require('/Users/kele/.config/yarn/global/node_modules/umi-build-dev/lib/plugins/404/NotFound.js')
          .default,
        { pagesPath: 'pages', hasRoutesInConfig: false },
      ),
  },
];
window.g_routes = routes;
const plugins = require('umi/_runtimePlugin');
plugins.applyForEach('patchRoutes', { initialValue: routes });

export { routes };

export default class RouterWrapper extends React.Component {
  unListen() {}

  constructor(props) {
    super(props);

    // route change handler
    function routeChangeHandler(location, action) {
      plugins.applyForEach('onRouteChange', {
        initialValue: {
          routes,
          location,
          action,
        },
      });
    }
    this.unListen = history.listen(routeChangeHandler);
    routeChangeHandler(history.location);
  }

  componentWillUnmount() {
    this.unListen();
  }

  render() {
    const props = this.props || {};
    return <Router history={history}>{renderRoutes(routes, props)}</Router>;
  }
}

react-umi_dva项目

react-umi_dva项目

带$前缀的文件或路径为动态路由

react-umi_dva项目

创建users目录,页面是动态路由

react-umi_dva项目

react-umi_dva项目

react-umi_dva项目

$id.js

import styles from "./$id.css";

export default function(props) {
  const { match } = props;
  console.log(match);
  return (
    <div className={styles.normal}>
      <h1>Page $id:: {match.params.id}</h1>
    </div>
  );
}

react-umi_dva项目

$post

react-umi_dva项目

commit.js

2号用户进入留言页

react-umi_dva项目

嵌套路由

react-umi_dva项目

react-umi_dva项目

react-umi_dva项目

react-umi_dva项目

_layout.js

props.children显示子内容插槽


import styles from './_layout.css';

export default function(props) {
  return (
    <div className={styles.normal}>
      <h1>Layout for ./users</h1>
      {
        props.children
      }
    </div>
  );
}

react-umi_dva项目

两个组件都会共享外面的layout

react-umi_dva项目

全局layout,约定src/layouts/index.js为全局路由,在pages文件夹外面创建layout

react-umi_dva项目

react-umi_dva项目

react-umi_dva项目

layouts/index.js,共享布局,login有自己的布局不要共享全局的布局

react-umi_dva项目

import styles from "./index.css";
import { Layout } from "antd";
import { Menu } from "antd";

const { Header, Footer, Content } = Layout;
import Link from "umi/link";
export default function(props) {
  const { location } = props;
  if (location.pathname === "/login") {
    return <>{props.children}</>;
  }
  return (
    <Layout>
      {/* 页头 */}
      <Header className={styles.header}>
        {/* 新增内容 */}
        <img
          className={styles.logo}
          src="https://img.kaikeba.com/logo-new.png"
        />
        <Menu
          theme="dark"
          mode="horizontal"
          defaultSelectedKeys={["2"]}
          style={{ lineHeight: "64px", float: "left" }}
        >
          <Menu.Item key="/goods">
            <Link to="/goods">商品</Link>
          </Menu.Item>
          <Menu.Item key="/users">
            <Link to="/users">用户</Link>
          </Menu.Item>
          <Menu.Item key="/about">
            <Link to="/about">关于</Link>
          </Menu.Item>
        </Menu>
      </Header>
      {/* 内容 */}
      <Content className={styles.content}>
        <div className={styles.box}>{props.children}</div>
      </Content>
      {/* 页脚 */}
      <Footer className={styles.footer}>开课吧</Footer>
    </Layout>
  );
  //   const { location } = props;
  //   if (location.pathname === "/login") {
  //     return <>{props.children}</>;
  //   }
  //   return (
  //     <div className={styles.normal}>
  //       <h1>Layout for /</h1>
  //       {props.children}
  //     </div>
  //   );
}

全局layouts包含所有的页面

react-umi_dva项目

react-umi_dva项目

(404页面&)

404页面&

react-umi_dva项目

404.js

import { Exception } from "ant-design-pro";
export default function() {
  return <Exception type="404" backText="返回首页"></Exception>;
}

react-umi_dva项目

扩展路由

react-umi_dva项目

react-umi_dva项目

多了一个title属性

react-umi_dva项目

react-umi_dva项目

配置式路由与约定式路由是冲突的

react-umi_dva项目

config/config.js ,根路径不再是index而是abouts

export default {
  //   routes: [{ path: "/", component: "./about" }]
  plugins: [
    [
      "umi-plugin-react",
      {
        dva: true,
        antd: true
      }
    ]
  ]
};

routes的默认路径都在pages中

react-umi_dva项目

react-umi_dva项目

路由守卫,增加routes字段格式是一个数组,pages下所有约定式路由全部失效

react-umi_dva项目

react-umi_dva项目

pages/about.js yml 数组是-

/*
 *
 * title: "about page"
 * Routes:
 *  - ./routes/PrivateRoute.js
 *  - ./routes/Test.js
 *
 *
 *
 */

import styles from "./about.less";

export default function() {
  return (
    <div className={styles.normal}>
      <h1>Page about</h1>
    </div>
  );
}

routes/PrivateRoute.js

export default props => {
  return (
    <div>
      <h3>PrivateRoute Page</h3>
      {props.children}
    </div>
  );
};

routes/Test.js

export default props => {
  return <div>test page {props.children}</div>;
};

自动生成配置式路由,路由组件让about.js组件包含PrivateRoute组件,设置路由守卫

react-umi_dva项目

最外面是PrivateRoute,里面是testRoute,最里面是

react-umi_dva项目

react-umi_dva项目

(配置configu路由&)

配置configu路由&

react-umi_dva项目

react-umi_dva项目

react-umi_dva项目

react-umi_dva项目

pages/index.js

import styles from "./index.css";
import Link from "umi/link";
export default function() {
  return (
    <div className={styles.normal}>
      <h1>Page index</h1>
      <Link to="/login">登录</Link>
    </div>
  );
}

pages/login.js

import React, { Component } from "react";
// import { Button } from "antd";
import styles from "./login.css";
import router from "umi/router";
import { Login } from "ant-design-pro";

import { connect } from "dva";

const { UserName, Password, Submit } = Login; // 通用的用户名、密码和提交组件

export default connect()(function(props) {
  // let from = props.location.state.from || "/"; // 重定向地址
  const onSubmit = (err, values) => {
    console.log("用户输入:", values);
    if (!err) {
      // 校验通过,提交登录
      props.dispatch({ type: "user/login", payload: values });
    }
  };
  return (
    <div className={styles.loginForm}>
      {/* logo */}
      <img className={styles.logo} src="https://img.kaikeba.com/logo-new.png" />
      {/* 登录表单 */}
      <Login onSubmit={onSubmit}>
        <UserName
          name="username"
          placeholder="kaikeba"
          rules={[{ required: true, message: "请输入用户名" }]}
        />
        <Password
          name="password"
          placeholder="123"
          rules={[{ required: true, message: "请输入密码" }]}
        />
        <Submit>登录</Submit>
      </Login>
    </div>
  );
});

react-umi_dva项目

(mock数据&)

mock数据&

react-umi_dva项目

react-umi_dva项目

 

react-umi_dva项目

时候mock.js来模拟生成随机数据

import mockjs from "mockjs";

export default {
  "GET /api/users": { users: [1, 2] },
  "GET /api/users/create": (req, res) => {
    setTimeout(() => {
      res.send("Ok");
    }, 1000);
  },
  "GET /api/tags": mockjs.mock({
    "list|100": [{ name: "@city", "value|1-100": 50, "type|0-2": 1 }]
  }),
  //动态的mock
  "/api/random": (req, res) => {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.send(
      mockjs.mock({
        // 每次请求均产生随机值
        "number|1-100": 100
      })
    );
  }
};

mock/goods.js

let data = [{ title: "web全栈" }, { title: "java架构师" }];

export default {
  "GET /api/goods": function(req, res) {
    setTimeout(() => {
      res.json({ result: data });
    }, 1000);
  }
};

添加跨域请求头,在别的服务请求mock的数据

react-umi_dva项目

允许跨域操作

import mockjs from "mockjs";

export default {
  "GET /api/users": { users: [1, 2] },
  "GET /api/users/create": (req, res) => {
    setTimeout(() => {
      res.send("Ok");
    }, 1000);
  },
  "GET /api/tags": mockjs.mock({
    "list|100": [{ name: "@city", "value|1-100": 50, "type|0-2": 1 }]
  }),
  //动态的mock
  "/api/random": (req, res) => {
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.send(
      mockjs.mock({
        // 每次请求均产生随机值
        "number|1-100": 100
      })
    );
  }
};

umi插件集

react-umi_dva项目

config/config.js 开启插件,开启dva

export default {
  //   routes: [{ path: "/", component: "./about" }]
  plugins: [
    [
      "umi-plugin-react",
      {
        dva: true,
        antd: true
      }
    ]
  ]
};

(Dva&)

Dva&

react-umi_dva项目

models/goods.js 通过namespace来区分,不能直接修改state,通过reducers来修改state

export default {
  namespace: "goods",
  state: {
    // 初始状态包括课程和分类
    courses: {}, // 课程
    tags: [] // 分类
  },
  effects: {
    *getList(action, { call, put }) {
      // 解构出courseData并初始化状态
      const {
        data: { data: courseData }
      } = yield call(getGoods);
      yield put({ type: "initGoods", payload: courseData });
    }
  },
  reducers: {
    initGoods(state, { payload }) {
      // 解构出tags和courses并返回
      const { tags, data: courses } = payload;
      return { ...state, tags, courses };
    }
  }
};

react-umi_dva项目

models/users.js

import axios from "axios";
import router from "umi/router";

// 初始状态:本地缓存或空值对象
const userinfo = JSON.parse(localStorage.getItem("userinfo")) || {
  token: "",
  role: "",
  username: "",
  balance: 0
};

// 登录请求方法
function login(payload) {
  return axios.post("/api/login", payload);
}

export default {
  namespace: "user", // 可省略
  state: userinfo,
  effects: {
    // action: user/login
    *login({ payload }, { call, put }) {
      const {
        data: { code, data: userinfo }
      } = yield call(login, payload);
      if (code == 0) {
        // 登录成功: 缓存用户信息
        localStorage.setItem("userinfo", JSON.stringify(userinfo));
        yield put({ type: "init", payload: userinfo });
        router.push("/");
      } else {
        // 登录失败:弹出提示信息,可以通过响应拦截器实现
      }
    }
  },
  reducers: {
    init(state, action) {
      // 覆盖旧状态
      return action.payload;
    }
  }
};

组件里使用model通过connect

react-umi_dva项目

pages/goods.js goods页面,把状态映射到组件里

react-umi_dva项目

第一个参数映射属性返回一个state对象,通过state.goods(namespace)取到对应的state

function() goodsList已经映射到props中,直接解构{goodLists}

函数式组件在页面渲染前发送请求通过Hooks

import styles from "./goods.css";

import { connect } from "dva";
import { useEffect } from "react";

export default connect(
  state => ({
    goodList: state.goods,
    usersList: state.users,
    loading: state.loading
  }),
  {
    addGood: title => {
      return {
        type: "goods/addGoods",
        payload: { title }
      };
    },
    getList: () => {
      return {
        type: "goods/getList"
      };
    }
  }
)(function({ goodList, usersList, addGood, getList, loading }) {
  // console.log(usersList[0].name);
  useEffect(() => {
    getList();
  }, []);
  if (loading.models.goods) {
    return <div>加载中...</div>;
  }

  return (
    <div className={styles.normal}>
      <h1>Page goods</h1>
      <ul>
        {goodList.map((item, index) => {
          return <li key={index}>{item.title}</li>;
        })}
      </ul>
      <button
        onClick={() => {
          addGood("商品" + new Date().getTime());
        }}
      >
        添加商品
      </button>
    </div>
  );
});

mock/goods.js

let data = [{ title: "web全栈" }, { title: "java架构师" }];

export default {
  "GET /api/goods": function(req, res) {
    setTimeout(() => {
      res.json({ result: data });
    }, 1000);
  }
};

actions返回promise

react-umi_dva项目

react-umi_dva项目

dva-loading加载状态,直接把loading映射到组件上

react-umi_dva项目

(Reat购物车项目实战&)

Reat购物车项目实战&

step01 - 迁出种子项目
git clone -b step01 https://github.com/57code/umi-test.git
umi配置,开启dva和antd

react-umi_dva项目

step02 - 创建布局页

antd布局组件

https://ant.design/components/layout-cn/

修改layouts/index.js,所有的子组件通过props放到Content共同拥有一个导航和页尾
import { Layout } from "antd";
import styles from "./index.css";
const { Header, Footer, Content } = Layout;
export default function(props) {
 return (
 <Layout>
 {/* ⻚头 */}
 <Header className={styles.header}>导航</Header>
 {/* 内容 */}
 <Content className={styles.content}>
 <div className={styles.box}>{props.children} </div>
 </Content>
 {/* ⻚脚 */}
 <Footer className={styles.footer}>开课吧</Footer>
 </Layout>
 );
}
import styles from "./index.css";
import { Layout } from "antd";
import { Menu } from "antd";

const { Header, Footer, Content } = Layout;
import Link from "umi/link";
export default function(props) {
  const { location } = props;
  if (location.pathname === "/login") {
    return <>{props.children}</>;
  }
  return (
    <Layout>
      {/* 页头 */}
      <Header className={styles.header}>
        {/* 新增内容 */}
        <img
          className={styles.logo}
          src="https://img.kaikeba.com/logo-new.png"
        />
        <Menu
          theme="dark"
          mode="horizontal"
          defaultSelectedKeys={["2"]}
          style={{ lineHeight: "64px", float: "left" }}
        >
          <Menu.Item key="/goods">
            <Link to="/goods">商品</Link>
          </Menu.Item>
          <Menu.Item key="/users">
            <Link to="/users">用户</Link>
          </Menu.Item>
          <Menu.Item key="/about">
            <Link to="/about">关于</Link>
          </Menu.Item>
        </Menu>
      </Header>
      {/* 内容 */}
      <Content className={styles.content}>
        <div className={styles.box}>{props.children}</div>
      </Content>
      {/* 页脚 */}
      <Footer className={styles.footer}>开课吧</Footer>
    </Layout>
  );
  //   const { location } = props;
  //   if (location.pathname === "/login") {
  //     return <>{props.children}</>;
  //   }
  //   return (
  //     <div className={styles.normal}>
  //       <h1>Layout for /</h1>
  //       {props.children}
  //     </div>
  //   );
}

样式设置,layouts/index.css

.header {
 color: white; }
.content {
 margin: 16px; }
.box {
锦绣课堂-React课程
 padding: 24px;
 background: #fff;
 min-height: 500px; }
.footer {
 text-align: center; }
.logo {
 float: left;
 width: 160px;
 margin: 16px 16px 0 0; }
配置式路由需要⼿动修改布局,config.js
//登录⻚⾯不需要布局,将登录路由配置移⾄顶层
{ path: "/login", component: "./login" },
{
 path: "/",
 component: "../layouts",
 routes: [
 // 移动之前路由配置到这⾥
 ]
}
export default {
  //   routes: [{ path: "/", component: "./about" }]
  plugins: [
    [
      "umi-plugin-react",
      {
        dva: true,
        antd: true
      }
    ]
  ]
};

导航菜单移动⾄顶部,layouts/index.js

import styles from "./index.css";
import { Layout } from "antd";
import { Menu } from "antd";

const { Header, Footer, Content } = Layout;
import Link from "umi/link";
export default function(props) {
  const { location } = props;
  if (location.pathname === "/login") {
    return <>{props.children}</>;
  }
  return (
    <Layout>
      {/* 页头 */}
      <Header className={styles.header}>
        {/* 新增内容 */}
        <img
          className={styles.logo}
          src="https://img.kaikeba.com/logo-new.png"
        />
        <Menu
          theme="dark"
          mode="horizontal"
          defaultSelectedKeys={["2"]}
          style={{ lineHeight: "64px", float: "left" }}
        >
          <Menu.Item key="/goods">
            <Link to="/goods">商品</Link>
          </Menu.Item>
          <Menu.Item key="/users">
            <Link to="/users">用户</Link>
          </Menu.Item>
          <Menu.Item key="/about">
            <Link to="/about">关于</Link>
          </Menu.Item>
        </Menu>
      </Header>
      {/* 内容 */}
      <Content className={styles.content}>
        <div className={styles.box}>{props.children}</div>
      </Content>
      {/* 页脚 */}
      <Footer className={styles.footer}>开课吧</Footer>
    </Layout>
  );
  //   const { location } = props;
  //   if (location.pathname === "/login") {
  //     return <>{props.children}</>;
  //   }
  //   return (
  //     <div className={styles.normal}>
  //       <h1>Layout for /</h1>
  //       {props.children}
  //     </div>
  //   );
}
step03 - ⽤户登录认证
利⽤ant-design-pro中Login、Exception、图表等业务组件加速开发进
引⼊ant-design-pro,安装: npm install ant-design-pro --save
测试:修改404⻚⾯提示内容,404.js
react-umi_dva项目
// umi的配置,已经⾃动⽀持antd-pro的按需加载
import { Exception } from "ant-design-pro";
export default function() {
  return <Exception type="404" backText="返回首页"></Exception>;
}

react-umi_dva项目

登录页构建,pages/login.js
import React, { Component } from "react";
// import { Button } from "antd";
import styles from "./login.css";
import router from "umi/router";
import { Login } from "ant-design-pro";

import { connect } from "dva";

const { UserName, Password, Submit } = Login; // 通用的用户名、密码和提交组件

export default connect()(function(props) {
  // let from = props.location.state.from || "/"; // 重定向地址
  const onSubmit = (err, values) => {
    console.log("用户输入:", values);
    if (!err) {
      // 校验通过,提交登录
      props.dispatch({ type: "user/login", payload: values });
    }
  };
  return (
    <div className={styles.loginForm}>
      {/* logo */}
      <img className={styles.logo} src="https://img.kaikeba.com/logo-new.png" />
      {/* 登录表单 */}
      <Login onSubmit={onSubmit}>
        <UserName
          name="username"
          placeholder="kaikeba"
          rules={[{ required: true, message: "请输入用户名" }]}
        />
        <Password
          name="password"
          placeholder="123"
          rules={[{ required: true, message: "请输入密码" }]}
        />
        <Submit>登录</Submit>
      </Login>
    </div>
  );
});

pages/login.css

.loginForm {
  width: 50%;
  margin: 100px auto;
  text-align: center;
}
.logo {
  margin-bottom: 30px;
}
登录接⼝mock,创建./mock/login.js
登录失败处理
react-umi_dva项目
返回json数据,输入错误401被拦截
export default {
  "post /api/login"(req, res, next) {
    const { username, password } = req.body;
    console.log(username, password);
    if (username == "kaikeba" && password == "123") {
      return res.json({
        code: 0,
        data: {
          token: "kaikebaisgood",
          role: "admin",
          balance: 1000,
          username: "kaikeba"
        }
      });
    }
    if (username == "jerry" && password == "123") {
      return res.json({
        code: 0,
        data: {
          token: "kaikebaisgood",
          role: "user",
          balance: 100,
          username: "jerry"
        }
      });
    }
    return res.status(401).json({
      code: -1,
      msg: "密码错误"
    });
  }
};

layouts/index.js 判断是/login,不让布局组件包裹login

import styles from "./index.css";
import { Layout } from "antd";
import { Menu } from "antd";

const { Header, Footer, Content } = Layout;
import Link from "umi/link";
export default function(props) {
  const { location } = props;
  if (location.pathname === "/login") {
    return <>{props.children}</>;
  }
  return (
    <Layout>
      {/* 页头 */}
      <Header className={styles.header}>
        {/* 新增内容 */}
        <img
          className={styles.logo}
          src="https://img.kaikeba.com/logo-new.png"
        />
        <Menu
          theme="dark"
          mode="horizontal"
          defaultSelectedKeys={["2"]}
          style={{ lineHeight: "64px", float: "left" }}
        >
          <Menu.Item key="/goods">
            <Link to="/goods">商品</Link>
          </Menu.Item>
          <Menu.Item key="/users">
            <Link to="/users">用户</Link>
          </Menu.Item>
          <Menu.Item key="/about">
            <Link to="/about">关于</Link>
          </Menu.Item>
        </Menu>
      </Header>
      {/* 内容 */}
      <Content className={styles.content}>
        <div className={styles.box}>{props.children}</div>
      </Content>
      {/* 页脚 */}
      <Footer className={styles.footer}>开课吧</Footer>
    </Layout>
  );
  //   const { location } = props;
  //   if (location.pathname === "/login") {
  //     return <>{props.children}</>;
  //   }
  //   return (
  //     <div className={styles.normal}>
  //       <h1>Layout for /</h1>
  //       {props.children}
  //     </div>
  //   );
}
用户信息保存和登录动作编写,创建./src/models/user.js
import axios from "axios";
import router from "umi/router";

// 初始状态:本地缓存或空值对象
const userinfo = JSON.parse(localStorage.getItem("userinfo")) || {
  token: "",
  role: "",
  username: "",
  balance: 0
};

// 登录请求方法
function login(payload) {
  return axios.post("/api/login", payload);
}

export default {
  namespace: "user", // 可省略
  state: userinfo,
  effects: {
    // action: user/login
    *login({ payload }, { call, put }) {
      const {
        data: { code, data: userinfo }
      } = yield call(login, payload);
      if (code == 0) {
        // 登录成功: 缓存用户信息
        localStorage.setItem("userinfo", JSON.stringify(userinfo));
        yield put({ type: "init", payload: userinfo });
        router.push("/");
      } else {
        // 登录失败:弹出提示信息,可以通过响应拦截器实现
      }
    }
  },
  reducers: {
    init(state, action) {
      // 覆盖旧状态
      return action.payload;
    }
  }
};

pages/login.js

import React, { Component } from "react";
// import { Button } from "antd";
import styles from "./login.css";
import router from "umi/router";
import { Login } from "ant-design-pro";

import { connect } from "dva";

const { UserName, Password, Submit } = Login; // 通用的用户名、密码和提交组件

export default connect()(function(props) {
  // let from = props.location.state.from || "/"; // 重定向地址
  const onSubmit = (err, values) => {
    console.log("用户输入:", values);
    if (!err) {
      // 校验通过,提交登录
      props.dispatch({ type: "user/login", payload: values });
    }
  };
  return (
    <div className={styles.loginForm}>
      {/* logo */}
      <img className={styles.logo} src="https://img.kaikeba.com/logo-new.png" />
      {/* 登录表单 */}
      <Login onSubmit={onSubmit}>
        <UserName
          name="username"
          placeholder="kaikeba"
          rules={[{ required: true, message: "请输入用户名" }]}
        />
        <Password
          name="password"
          placeholder="123"
          rules={[{ required: true, message: "请输入密码" }]}
        />
        <Submit>登录</Submit>
      </Login>
    </div>
  );
});

~ 响应拦截,创建./src/interceptor.js,通过notification通知组件进行弹窗

import axios from "axios";
import { notification } from "antd";

const codeMessage = {
  202: "一个请求已经进入后台排队(异步任务)。",
  401: "用户没有权限(令牌、用户名、密码错误)。",
  404: "发出的请求针对的是不存在的记录,服务器没有进行操作。",
  500: "服务器发生错误,请检查服务器。"
};

// 仅拦截异常状态响应
axios.interceptors.response.use(null, ({ response }) => {
  if (codeMessage[response.status]) {
    notification.error({
      message: `请求错误 ${response.status}: ${response.config.url}`,
      description: codeMessage[response.status]
    });
  }
  return Promise.reject(err);
});

src/global.js 执行拦截器带需要创建global

// 全局入口
import interceptor from "./interceptor";

react-umi_dva项目

react-umi_dva项目

商品列表

mock/goods.js

let data = [{ title: "web全栈" }, { title: "java架构师" }];

export default {
  "GET /api/goods": function(req, res) {
    setTimeout(() => {
      res.json({ result: data });
    }, 1000);
  }
};

models/goods.js

export default {
  namespace: "goods",
  state: {
    // 初始状态包括课程和分类
    courses: {}, // 课程
    tags: [] // 分类
  },
  effects: {
    *getList(action, { call, put }) {
      // 解构出courseData并初始化状态
      const {
        data: { data: courseData }
      } = yield call(getGoods);
      yield put({ type: "initGoods", payload: courseData });
    }
  },
  reducers: {
    initGoods(state, { payload }) {
      // 解构出tags和courses并返回
      const { tags, data: courses } = payload;
      return { ...state, tags, courses };
    }
  }
};

pages/goods/index.js

import { TagSelect } from "ant-design-pro";
import { connect } from "dva";
import React from "react";
// @connect(
//   state => ({
//     courses: state.goods.courses, // 映射课程数据
//     tags: state.goods.tags // 映射标签数据
//   }),
//   {}
// )

export default connect(
  state => ({
    courses: state.goods.courses, // 映射课程数据
    tags: state.goods.tags // 映射标签数据
  }),
  {}
)(
  class GoodsTest extends React.Component {
    // 页签变更
    tagSelectChange = tags => {
      console.log(tags);
    };
    render() {
      if (this.props.loading.models.goods) {
        return <div>加载中...</div>;
      }
      return (
        <div>
          {/* 分类标签 */}
          <TagSelect onChange={this.tagSelectChange}>
            {this.props.tags.map(tag => {
              return (
                <TagSelect.Option key={tag} value={tag}>
                  {tag}
                </TagSelect.Option>
              );
            })}
          </TagSelect>
        </div>
      );
    }
  }
);
显示课程列表,src\pages\goods.js
import styles from "./goods.css";

import { connect } from "dva";
import { useEffect } from "react";

export default connect(
  state => ({
    goodList: state.goods,
    usersList: state.users,
    loading: state.loading
  }),
  {
    addGood: title => {
      return {
        type: "goods/addGoods",
        payload: { title }
      };
    },
    getList: () => {
      return {
        type: "goods/getList"
      };
    }
  }
)(function({ goodList, usersList, addGood, getList, loading }) {
  // console.log(usersList[0].name);
  useEffect(() => {
    getList();
  }, []);
  if (loading.models.goods) {
    return <div>加载中...</div>;
  }

  return (
    <div className={styles.normal}>
      <h1>Page goods</h1>
      <ul>
        {goodList.map((item, index) => {
          return <li key={index}>{item.title}</li>;
        })}
      </ul>
      <button
        onClick={() => {
          addGood("商品" + new Date().getTime());
        }}
      >
        添加商品
      </button>
    </div>
  );
});
import { Card, Row, Col, Skeleton, Icon } from "antd";
class Goods extends Component {
 constructor(props) {
 super(props);
 // displayCourses为需要显示的商品数组
 this.state = {
 displayCourses: new Array(8).fill({}) // 填充数组⽤
于⻣架屏展示
 };
 }
 // 数据传⼊时执⾏⼀次tagSelectChange
 componentWillReceiveProps(props){ 
 if(props.tags.length){
 this.tagSelectChange(props.tags, props.courses)
 }
 }
 // 额外传⼊课程列表数据
 tagSelectChange = (tags, courses = this.props.courses)
=> {
 console.log(tags);
 // 过滤出要显示的数据
 let displayCourses = [];
 tags.forEach(tag => {
 displayCourses = [...displayCourses,
...courses[tag]];
 });
 this.setState({ displayCourses });
 console.log(displayCourses); 
 };
 render() {
 // 使⽤⻣架屏做加载反馈,loading属性不再需要
 // if (this.props.loading.models.goods) {
锦绣课堂-React课程
 // return <div>加载中...</div>;
 // }
 return (
 <div>
 {/* 分类标签 */}
 {/* 商品列表 */}
 <Row type="flex" justify="start">
 {this.state.displayCourses.map((item, index)
=> {
 return (
 <Col key={index} style={{ padding: 10 }}
span={6}>
 {item.name ? (
 <Card
 hoverable
 title={item.name}
 cover={<img src={"/course/" +
item.img} />}
 >
 <Card.Meta
 description={
 <div>
 <span>¥{item.price}</span>
 <span style={{ float: "right"
}}>
 <Icon type="user" />
{item.solded}
 </span>
 </div>
 }
 />
 <div />
 </Card>
 ) : (
 <Skeleton active={true} />
 )}
 </Col>
 })}
 </Row>
 </div>
 );
 }
}
export default Goods;
TagSelect初始状态调整:默认应当全选

 

constructor(props) {
 super(props);
 this.state = {
 //...
 tags: [], // 默认未选中任何标签
 };
}
tagSelectChange = (tags, courses = this.props.courses)
=> {
 // 用户行为修改状态
 this.setState({ displayCourses, tags }); 
};
// 组件受控
<TagSelect value={this.state.tags}>
step05 - 添加购物车
创建购物车模型,./src/models/cart.js
export default {
 namespace: "cart", // 可省略
 state: JSON.parse(localStorage.getItem("cart")) || [],
// 初始状态:缓存或空数组
 reducers: {
 addCart(cart, action) {
 const good = action.payload;
 const idx = cart.findIndex(v => v.id == good.id);
 if (idx > -1) {
 // 更新数量
 const cartCopy = [...cart];
 const itemCopy = { ...cartCopy[idx] };
 itemCopy.count += 1;
 cartCopy.splice(idx, 1, itemCopy);
 return cartCopy;
 } else {
 // 新增
 return [...cart, { ...good, count: 1 }];
 }
 }
 }
};
请求添加购物车,src\pages\goods\index.js
@connect(
 state => ({ ... }),
 {
 addCart: item => ({ // 加购⽅法
 type: "cart/addCart",
 payload: item
 }),
 }
)
class Goods extends Component {
 addCart = (e, item) => {
 e.stopPropagation();
 this.props.addCart(item);
 };
 render() {
 <Card extra={
 <Icon onClick={e => this.addCart(e, item)}
 type="shopping-cart"
 style={{ fontSize: 18 }} />}>
 }
}

购物车数据同步到localStorage

effects: {
 *addCart({payload}, {put, select}) { 
 yield put({type: 'add', payload})
 const cart = yield select(state => state.cart);
 localStorage.setItem("cart",
JSON.stringify(cart));
 }
 },
 reducers: {
 add(cart, action) {...}
 }
页头添加购物车信息,./src/layouts/index.js
import {Badge, Icon} from 'antd';
export default function(props) {
 render(){
 ...
 <div style={{float:'right'}}>
 <Icon type="shopping-cart" style=
{{fontSize:18}}/>
 <span>我的购物⻋</span>
 <Badge count={5} offset={[-4,-18]}/>
 </div>
 } }
~ 希望徽章小一点,添加全局样式,./src/global.css
.ant-badge-count{
 height: 16px;
 border-radius: 8px;
 min-width: 16px;
 line-height: 16px;
 padding: 0; }

~ 显示购物车数据

import { Dropdown } from "antd";
export default connect(state => ({ // 连接购物⻋状态
 count: state.cart.length,
 cart: state.cart
}))(function(props) {
 render(){
 // 构造购物⻋列表菜单
 const menu = (
 <Menu>
 {this.props.cart.map((item, index) => (
 <Menu.Item key={index}>
 {item.name}×{item.count} <span>¥ {item.count * item.price}</span>
 </Menu.Item>
 ))}
 </Menu>
 );
 ...
 return (
 {/* 购物⻋信息放在Dropdown以便展示 */}
 <Dropdown overlay={menu}
placement="bottomRight">
 <div className={styles.cart}>
 {/* 购物⻋项⽬数量 */} 
 <Badge count={this.props.count} offset=
{[-4, -18]} />
 </div>
 </Dropdown>
 )
 } })

 

相关标签: Dva学习手册