Web全栈架构师(二)——React学习笔记(2)

状态管理

redux

学习资源

  • redux:https://redux.js.org/
  • react-redux:https://github.com/reduxjs/react-redux

原理图

在这里插入图片描述

试用redux

  • 安装:npm install redux --save
  • redux中首先需要了解 store,store具有全局唯一性,所有的数据都在这一个数据源里进行管理,但是本身 redux 与 react 并没有直接的联系,可以单独使用。
  • 复杂的项目才需要 redux 来管理数据,简单项目 state + props + context 足矣。
redux中的角色
  • Store:状态载体,访问状态、提交状态更新、监听状态更新
  • Reducer:状态更新具体执行者,纯函数
  • Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作
关键点
  • 需要一个 store 来存储数据
  • store 里的 state 是放置数据的地方
  • 通过 dispatch 一个 action 来提交对数据的修改
  • 请求提交到 reducer 函数里,根据传入的 action 和 state,返回新的 state
示例

用一个累加器来举例:

  • store.js
import {createStore} from 'redux'

// reducer:具体状态修改执行者
const counterReducer = (state = 0, action) => {
  switch(action.type){
    case 'incr': 
      return state + 1;
    case 'decr': 
      return state - 1;
    default: 
      return state;
  }
}
export default createStore(counterReducer);
  • ReduxTest
import React, { Component } from 'react'
import store from '../store'

export default class ReduxTest extends Component {
  render() {
    return (
      <div>
        <p>{store.getState()}</p>
        <div>
          <button onClick={() => store.dispatch({type: 'decr'})}>-</button>
          <button onClick={() => store.dispatch({type: 'incr'})}>+</button>
        </div>
      </div>
    )
  }
}
  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ReduxTest from './components/ReduxTest'

function render() {
  ReactDOM.render(<ReduxTest/>, document.querySelector('#root'));
}
render();
store.subscribe(render)
  • 涉及知识点:
* createStore
* reducer
* getState
* dispatch
* subscribe

react-redux

  • 通过上面的案例可以看出,每次数据修改,都要重新调用 render,这样实在是不太友好。我们希望能用 react 的方式来写,可以安装 react-redux 的支持:npm install react-redux --save

API

  • Provider:顶级组件,提供数据
  • connect:高阶组件,提供数据和方法

代码

  • index.js
import store from './store'
import {Provider} from 'react-redux'
function render() {
  ReactDOM.render((
    <Provider store={store}>
      <ReduxTest/>
    </Provider>
  ), document.querySelector('#root'));
}
render();
  • ReduxTest.js
import {connect} from 'react-redux'

class ReduxTest extends Component {
  render() {
    return (
      <div>
        <p>{this.props.num}</p>
        <div>
          <button onClick={() => this.props.decr()}>-</button>
          <button onClick={() => this.props.incr()}>+</button>
        </div>
      </div>
    )
  }
}

const mapStateToProps = state => ({num: state});
const mapDispatchToProps = dispatch => ({
  incr: () => dispatch({type: 'incr'}),
  decr: () => dispatch({type: 'decr'})
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ReduxTest)
  • 很明显,connect用装饰器会更简洁,声明需要 state 里的数据方法
// @connect(mapStateToProps, mapDispatchToProps)
@connect(
  state => ({num: state}),
  {
    incr: () => ({type: 'incr'}),
    decr: () => ({type: 'decr'})
  }
)
class ReduxTest extends Component {
  render() {
    return (
      <div>
        <p>{this.props.num}</p>
        <div>
          <button onClick={() => this.props.decr()}>-</button>
          <button onClick={() => this.props.incr()}>+</button>
        </div>
      </div>
    )
  }
}
export default ReduxTest

中间件:redux-thunk、redux-logger

  • redux默认只支持同步,实现异步任务(比如延迟、网络请求),需要中间件的支持,比如我们可以使用最简单的redux-thunk,同时还可以使用日志中间件redux-logger
  • 安装:npm install redux-thunk redux-logger --save
    在这里插入图片描述
  • Reducer:纯函数,只承担计算 state 的功能,不适合承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
  • View:与state一一对应,可以看作 state 的视觉层,也不适合承担其他功能。
  • Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
  • 实际的Reducer 和 action store 都需要独立拆分文件。

重构代码

  • src下新建文件夹 store,然后store下新建 index.js
import {createStore, applyMiddleware, combineReducers} from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
// import counterReducer from './counter.redux'
import counter from './counter.redux'

export default createStore(
  // reducer模块化
  combineReducers({counter}), 
  applyMiddleware(logger, thunk)
);
  • 在 store 下新建文件 counter.redux.js
// reducer:具体状态修改执行者
// const counterReducer = (state = 0, action) => {
export default (state = 0, action) => {
  switch(action.type){
    case 'incr': 
      return state + 1;
    case 'decr': 
      return state - 1;
    default: 
      return state;
  }
}
function incr() {
  return {type: 'incr'}
}
function decr() {
  return {type: 'decr'}
} 
function asyncAdd(){
  return (dispatch, getState) => {
    console.log(getState());
    // 模拟异步操作
    setTimeout(() => {
      dispatch({type: 'incr'})
    }, 1000)
  }
} 
export {incr, decr, asyncAdd}
  • 修改 ReduxTest.js
import React, { Component } from 'react'
import {connect} from 'react-redux'
import {incr, decr, asyncAdd} from '../store/counter.redux'

@connect(
  // 注意:这里使用 state 需要带上模块名 counter
  state => ({num: state.counter}),
  {incr, decr, asyncAdd}
)
class ReduxTest extends Component {
  render() {
    return (
      <div>
        <p>{this.props.num}</p>
        <div>
          <button onClick={() => this.props.decr()}>-</button>
          <button onClick={() => this.props.incr()}>+</button>
          <button onClick={() => this.props.asyncAdd()}>asyncAdd</button>
        </div>
      </div>
    )
  }
}
export default ReduxTest

中间件原理

redux原理
  • createStore
export function createStore(reducer, enhancer){
  if(enhancer){ // 增强器,作用:将createStore功能增强后返回【增强的createStore】,然后把参数reducer传入
    return enhancer(createStore)(reducer);
  }
  let currentState = {}
  let currentListeners = []
  function getState(){
    return currentState
  }
  fucntion subscribe(listener){
    currentListeners.push(listener)
  }
  fucntion dispatch(action){
    currentState = reducer(currentState, action)
    currentListeners.forEach(v => v());
    return action;
  }
  dispatch({type: '@IMOOC/WONIU-REDUX'}) // 初始化 state ==> default
  return {getState, subscribe, dispatch}
}
  • applyMiddleware
export function applyMiddleware(...middlewares){
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 获得中间件链式 数组
    const middlewareChain = middlewares.map(middleware => middleware(midApi))
    // compose作用:将链式的中间件,一层一层包装起来
    dispatch = compose(...middlewareChain)(store.dispatch)
    return {
      ...store,
      dispatch
    }
  }
}
export function compose(...funcs){
  if(funcs.length == 0){
    return arg => arg
  }
  if(funcs.length == 1){
    return funcs[0]
  }
  return funcs.reduce((ret, item) => (...args) => ret(item(...args)))
}

浅析Redux 的 store enhancer :https://www.jianshu.com/p/04d3fefea8d7
reduce与redux中compose函数:https://www.jianshu.com/p/c9dfe57c4a4e

  • bindActionCreators
function bindActionCreator(creator, dispatch){
  return (...args) => dispatch(creator(...args))
}
export function bindActionCreators(creators, dispatch){
  return Object.keys(creators).reduce((ret, item) => {
    ret[item] = bindActionCreator(creators[item], dispatch)
    return ret
  }, {})
}
react-redux原理
  • connect
import React from 'react'
import ProTypes from 'prop-types'
import {bindActionCreators} from './woniu-redux'

export const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => (WrapComponent) => {
  return class ConnectComponent extends React.Component {
    static contextTypes = {
      store: ProTypes.object
    }
    constructor(props, context){
      super(props, context)
      this.state = {
        props: {}
      }
    }
    componentDidMount(){
      const {store} = this.context
      store.subscribe(() => this.update())
      this.update()
    }
    update(){
      const {store} = this.context
      const StateProps = mapStateToProps(store.getState())
      const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
      this.setState({
        props: {
          ...this.state.props,
          ...StateProps,
          ...dispatchProps
        }
      })
    }
  }
  render(){
    return <WrapComponent {...this.state.props}></WrapComponent>
  }
}
  • Provider
export class Provider extends React.Component{
  static childContextTypes = {
    store: ProTypes.object
  }
  getChildContext(){
    return {stote: this.store}
  }
  constructor(props, context){
    super(props, context)
    this.store = props.store
  }
  render() {
    return this.props.children
  }
}
redux-thunk原理
  • thunk
const thunk = ({dispatch, getState}) => next => action => {
  if(typeof action === 'function'){
    return action(dispatch, getState)
  }
  return next(action)
}
export default thunk

路由管理

react-router-dom

  • 特点:路由也是组件分布式配置包含式匹配
  • 学习资源:https://reacttraining.com/react-router
  • 安装:npm i -S react-router-dom

使用

基本使用

  • 知识点:BrowserRouter、路由链接地址(Link)、路由展示(Route)、路由匹配(包含匹配、确切匹配、Switch)、路由重定向、路由传参
  • BrowserRouter
import React, { Component } from "react";
import { BrowserRouter, Link, Route, Switch, Redirect } from "react-router-dom";

export default class RouteSample extends Component {
  render() {
    return (
      <BrowserRouter>
        <App />
      </BrowserRouter>
    );
  }
}
  • 路由根组件——App
function App(props) {
  return (
    <div>
      {/* 导航链接 */}
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/about">About</Link></li>
        <li><Link to="/foo">Foo</Link></li>
      </ul>
      {/* 路由配置 */}
      <Switch>
        {/* exact:确切匹配 */}
        <Route exact path="/" path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/detail/:course" component={Detail} />
        <Route path="/login" component={Login} />
        <Route component={NoMatch} />
      </Switch>
    </div>
  );
}
  • Home
function Home({ location }) {
  console.log("接收参数:", location.state && location.state.foo);

  return (
    <div>
      <ul>
        <li><Link to="/detail/web">Web</Link></li>
        <li><Link to="/detail/python">Python</Link></li>
        <li><Link to="/detail/java">Java</Link></li>
      </ul>
    </div>
  );
}
  • About
function About() {
  return (
    <div>
      {/* 显示用户信息和订单 */}
      <h2>用户中心</h2>
      <div>
        <Link to="/about/me">个人信息</Link><br />
        <Link to="/about/order">订单</Link>
      </div>
      <Switch>
        <Route path="/about/me" component={() => <div>我的信息</div>} />
        <Route path="/about/order" component={() => <div>订单信息</div>} />
        {/* 重定向 */}
        <Redirect to="/about/me" />
      </Switch>
    </div>
  );
}
  • Detail
// function Detail(props) {
function Detail({ match, history, location }) {
  /**
   * match      参数获取等路由信息
   * history    导航
   * location   url定位
   */
  console.log(match, history, location);
  return (
    <div>
      {/* 获取参数 */}
      {match.params.course}
      {/* 命令式导航 */}
      <button onClick={history.goBack}>返回</button>
      {/* <button onClick={() => history.push('/')}>返回首页</button> */}
      <button onClick={() => history.push({ pathname: "/", state: { foo: "bar" } })}>返回首页</button>
    </div>
  );
}
  • 404页面
function NoMatch() {
  return <div>404页面</div>;
}

路由守卫

  • 需求:完成登录验证,对 About 进行守卫,没有登录则跳到登陆页面,登陆页面点击登陆则跳回About页面
  • 修改
{/* <Route path="/about" component={About} /> */}
<PrivateRoute path="/about" component={About} />
  • PrivateRoute
/**
 * 路由守卫:定义可以验证的高阶组件
 */
function PrivateRoute({ component: Component, ...rest }) {
  // render 和 component 二选一
  return (
    <Route {...rest} render={props => auth.isLogin 
        ? (<Component {...props} />) 
	    : (<Redirect to={{pathname: "/login", state: { from:props.location.pathname }}}/>)
      }
    />
  );
}
  • 登陆组件
// 模拟接口
const auth = {
  isLogin: false,
  login(cb) {
    this.isLogin = true;
    setTimeout(cb, 300);
  }
};
// 登陆组件
class Login extends Component {
  state = { isLogin: false };
  login = () => {
    auth.login(() => {
      this.setState({ isLogin: true });
    });
  };
  render() {
    // 回调地址
    const from = this.props.location.state.from || '/';
    
    if (this.state.isLogin) {
      return <Redirect to={from} />;
    }
    return (
      <div>
        <p>请先登陆</p>
        <button onClick={this.login}>登陆</button>
      </div>
    );
  }
}

redux改造

  • 使用 redux 改造用户登陆验证
  • 新建 user.redux.js
const initialState = {
  isLogin: false
}

export default (state = initialState, { type, payload }) => {
  switch (type) {

  case 'login':
    return { isLogin: true }

  default:
    return state
  }
}
export function login(){
  return (dispatch) => {
    // mock一个异步登陆
    setTimeout(() => {
      dispatch({type: 'login'})
    }, 1000);
  }
}
  • 在store中导入
import {createStore, applyMiddleware, combineReducers} from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import user from './user.redux'

export default createStore( 
	combineReducers({counter, user}),
	applyMiddleware(logger, thunk)
)
  • 登陆组件
@connect(
  state => ({ isLogin: state.user.isLogin }),
  { login }
)
class Login extends Component {
  render() {
    // 回调地址
    const from = this.props.location.state.from || "/";
    if (this.props.isLogin) {
      return <Redirect to={from} />;
    }
    return (
      <div>
        <p>请先登陆</p>
        <button onClick={this.props.login}>登陆</button>
      </div>
    );
  }
}
  • 路由守卫组件
@connect(state => ({ isLogin: state.user.isLogin }))
class PrivateRoute extends Component {
  render() {
    const {isLogin, component: Component, ...rest} = this.props;
    return (
      <Route {...rest} render={props => isLogin 
      	? (<Component {...props} />) 
      	: (<Redirect to={{pathname: "/login", state: { from: props.location.pathname }}}/>)
        }
      />
    );
  }
}
  • 修改BrowserRouter:注入store
export default class RouteSample extends Component {
  render() {
    return (
      <BrowserRouter>
        <Provider store={store}>
          <App />
        </Provider>
      </BrowserRouter>
    );
  }
}

redux-saga改造

  • 概述:redux-saga使副作用(数据获取、浏览器缓存获取)易于管理、执行、测试和失败处理
  • 地址:https://github.com/redux-saga/redux-saga
  • 安装:npm install --save redux-saga
  • 使用:用户登录改造
  • 新建 sagas.js
import {call, put, takeEvery} from 'redux-saga/effects'

const api = {
  login(){
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if(Math.random() > 0.5){
          resolve({id: 1, name: 'jerry'})
        }else{
          reject('用户名或密码错误')
        }
      }, 1000)
    })
  }
}
// work saga
function* login(action){
  try {
    const result = yield call(api.login)  // 调用接口
    yield put({type: 'login', result})	// dispatch派发action
  } catch (error) {
    yield put({type: 'login_failure', message: error.message})
  }
}
function* mySaga(){
  yield takeEvery('login_request', login) // 事件监听
}
export default mySaga
  • 在store中导入
import {createStore, applyMiddleware, combineReducers} from 'redux'
import logger from 'redux-logger'
import createSagaMiddleware from 'redux-saga'
import saga from './sagas'

// 1.创建中间件
const mid = createSagaMiddleware();
// 2.应用中间件
const store = createStore(
  // reducer模块化
  combineReducers({user}), 
  // applyMiddleware(logger, thunk)
  applyMiddleware(logger, mid)
);
// 3.run
mid.run(saga);
// 4.导出
export default store;
  • user.redux.js中修改
// // for redux-thunk
// export function login(){
//   return (dispatch) => {
//     // mock一个异步登陆
//     setTimeout(() => {
//       dispatch({type: 'login'})
//     }, 1000);
//   }
// }

// for saga
export function login(){
  return {type: 'login_request'}
}

redux-saga 和 redux-thunk的区别

  • thunk可以接收 function 类型的action,saga 则是纯对象 action 的解决方案
  • saga使用 generator 解决异步问题,非常容易用同步的方式编写异步代码
ES6的generator函数

简单的说生成器就是带一个星星的函数,生成器函数可以暂停,而普通函数则是默认一路到底执行代码,生成器函数在内部碰到 yield 就可以实现暂停的功能,使用 next 进行迭代。

function* g(){
	yield 'a';
	yield 'b';
	yield 'c';
	return 'ending';
}
console.log(g()); // 返回迭代器 Iterator

g()并不执行 g 函数,而是返回迭代器 Iterator 对象,调用 next 实际执行

var gen = g();
console.log(gen.next());  // {value: "a", done: false}
// value:是 yield后面表达式执行的结果;done:false-没执行完,true-执行完
console.log(gen.next());  // {value: "b", done: false}
console.log(gen.next());  // {value: "c", done: false}
console.log(gen.next());  // {value: "ending", done: true}
console.log(gen.next());  // {value: undefined, done: true}
// 使用递归函数执行 生成器函数 中的所有步骤
function next(){
  let {value, done} = gen.next() // 启动
  console.log(value)	// 依次打印输出 a b c ending
  if(!done){	// 直到迭代完成
    next()
  }
}
next()
  • 传值
function* say(){
  let a = yield '1'
  console.log(a)
  let b = yield '2'
  console.log(b)
}
let it = say()

// 输出 {value: "1", done: false}
// a的值并不是该返回值,而是下次next参数
console.log(it.next())

// 输出 我是被传进来的1
// 输出 {value: "2", done: false}
console.log(it.next('我是被传进来的1'))

// 输出 我是被传进来的2
// 输出 {value: undefined, done: true}
console.log(it.next('我是被传进来的2'))
  • 结合Promise使用
// 使用 Generator 顺序执行两次异步操作
function* r(num){
  const r1 = yield compute(num);
  yield compute(r1);
}
// compute为异步操作,结合Promise使用可以轻松实现异步操作队列
function compute(num){
  return new Promise(resolve => {
    setTimeout(() => {
      const ret = num * num;
      console.log(ret); // 输出处理结果
      resolve(ret); // 操作成功
    }, 1000)
  })
}
// 不使用递归函数调用
let it = r(2)
it.next().value.then(num => it.next(num));
// 输出:4 16
// 修改为可处理Promise的next
function next(data){
  let {value, done} = it.next(data); // 启动
  if(!done) {
    value.then(num => {
      next(num)
    })
  }
}
next();
// 输出:4 16

umi

网址:https://umijs.org/zh/guide

介绍

在这里插入图片描述

特性

在这里插入图片描述

架构图

在这里插入图片描述

从源码到上线的生命周期管理

市面上的框架基本都是从源码到构建产物,很少会考虑到各种发布流程,而 umi 则多走了这一步。下图是 umi 从源码到上线的一个流程。
在这里插入图片描述

他和 dva、roadhog 是什么关系?

umi 首先会加载用户的配置和插件,然后基于配置或者目录,生成一份路由配置,再基于此路由配置,把 JS/CSS 源码和 HTML 完整地串联起来。用户配置的参数和插件会影响流程里的每个环节。
他和 dva、roadhog 是什么关系?
简单来说,

  • roadhog 是基于 webpack 的封装工具,目的是简化 webpack 的配置
  • umi 可以简单地理解为 roadhog + 路由,思路类似 next.js/nuxt.js,辅以一套插件机制,目的是通过框架的方式简化 React 开发
  • dva 目前是纯粹的数据流,和 umi 以及 roadhog 之间并没有相互的依赖关系,可以分开使用也可以一起使用,个人觉得 umi + dva 是比较搭的

dva

原理图

redux + redux-saga + react-redux + react-router-dom = dva
在这里插入图片描述

dva+umi的约定

  • src源码
* pages页面
* components组件
* layout布局
* model
  • config配置
  • mock数据模拟
  • test测试等

umi使用

  • 全局安装:npm install umi -g
  • 新建页面:umi g page indexumi g page about
  • 开启服务查看效果:umi dev

访问index页面:http://localhost:8000/
访问about页面:http://localhost:8000/about

动态路由

  • 以$开头的文件或目录
  • 创建 users/$id.js,内容和其他页面相同,显示一下传参
export default function(props){
	return (
		<div>
			<h1>user id:{props.match.params.id}</h1>
		<div>
	)
}

嵌套路由

  • 创建父组件 umi g page users/_layout
export default function(props) {
  return (
    <div>
      <h1>Page _layout</h1>
      <div>{props.children}</div>
    </div>
  );
}

配置式路由

  • 默认路由为声明式,根据pages下面的内容自动生成路由,业务复杂后仍然需要配置路由
  • 创建 ./config/config.js
export default {
    routes: [
        // 相对路径是相对于pages
        { path: '/', component: './index' },
        {
            path: 'users',
            component: './users/_layout',
            routes: [
                { path: '/users/', component: './users/index' },
                { path: '/users/:id', component: './users/$id' }
            ]
        },
        // 配置 404 页面
        {component: './notfound'}
    ]
}
  • 注意:一旦使用了配置式路由,那么声明式路由就失效了。

路由守卫

  • 配置 ./config/config.js
export default {
    routes: [
        ... ,
        // 路由守卫
        {
            path: '/about', component: './about',
            Routes: ['./routes/PrivateRoute.js'] // 这里的是相对于根目录的,且后缀名不可省略
        },
        ...
    ]
}
  • 创建 ./routes/PrivateRoute.js
import Redirect from 'umi/redirect'

export default props => {
    if (Math.random() > 0.5) {
        return <Redirect to="/login" />
    }
    return (
        <div>
            {props.children}
        </div>
    )
}
  • 创建登录页面:umi g page login,并配置路由 { path: ‘login’, component: ‘./login’}
import styles from './login.css';

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

引入antd

  • 安装:npm install antd -Snpm install umi-plugin-react -D

win10 有权限错误,通过管理员权限打开 vscode

  • 修改 ./config/config.js
export default {
    plugins: [
        ['umi-plugin-react', {antd: true}]
    ],
    routes: [
        ...
    ]
}
  • 试用
import {Button} from 'antd'
export default () => {
	return <div><Button>登录</Button></div>
}

引入dva

  • 软件分层:回顾 react,为了让数据流更易于维护,我们分成了 store、reducer、action 等模块,各司其职软件开发也是一样,如下图是前端代码结构层次图:
  • Page:负责与用户直接打交道,渲染页面、接受用户的操作输入,侧重于展示型交互性逻辑。
  • Model:负责处理业务逻辑,为 Page 做数据、状态的读写、交换、暂存等。
  • Service:负责与 HTTP 接口对接,进行纯粹的数据读写。
    在这里插入图片描述
  • 修改 ./config/config.js 配置引入 dva:
export default {
  plugins: [
    ["umi-plugin-react",
      {
        antd: true,
        dva: true
      }
    ]
  ],
  routes: [
  	...
	{ path: "/goods", component: "./goods" },
	...
  ]
};
  • dva 是基于 redux、redux-saga 和 react-router 的轻量级前端框架,以及最佳实践沉淀。核心API如下
* model
	- state
	- action
	- dispatch
	- reducers
	- effects:处理异步 saga 中间件
	- subscriptions 订阅
	- router 路由
model
  • 新建 src/model/goods.js
export default {
	namespace: 'goods', // model的命名空间,区分多个model
	state: [], // 初始化状态
	effects: {}, // 异步操作
	reducers: { // 更新状态
	}
}
  • 说明:
* namespace:model的命名空间,只能用字符串。一个大型应用可能包含多个model,同过 namespace区分
* state:当前model状态的初始值,表示当前状态
* reducers:用于处理同步操作,可以修改 state,由 action 触发。reducer是一个纯函数,它接收当前的 state 及一个 action 对象。action对象里面可以包含数据体(payload)作为入参,需要返回一个新的 state。
* effects:用于处理异步操作(例如:与服务器交互)和业务逻辑,也是由 action 触发。但是,它不可以修改 state,要通过 触发 action 实现对 state 的间接操作。
* action:是 reducers 及 effects 的触发器,一般是一个对象,形如 {type: 'add', payload: todo},同过 type 属性可以匹配到具体某个 reducer 或者 effect,payload 属性则是数据体,用于传送给 reducer 或 effect。
  • 首先安装axios:npm i axios -S
import axios from "axios";

// api
function getGoods() {
  return axios.get("/api/goods");
}

export default {
  namespace: "goods", // model的名字空间,区分多个model
  state: [
    // 初始化状态
    { title: "xxx" }
  ],
  effects: {
    // 异步操作
    *getList(action, { call, put }) {
      const res = yield call(getGoods);
      // type名字不需要命名空间
      yield put({ type: 'initGoods', payload: res.data.result });
    }
  },
  reducers: {
    // 更新状态
    initGoods(state, action) {
      return action.payload;
    },
    addGood(state, action) {
      return [...state, { title: action.payload.title }];
    }
  }
};
商品页面 goods.js
import { Component } from 'react'
import { connect } from "dva";
import styles from './goods.css';
import { Button, Card } from 'antd';

@connect(
  state => ({
    loading: state.loading, // 通过loading命名空间获取加载状态
    goodsList: state.goods // 获取指定命名空间的模型状态
  }),
  {
    addGood: title => ({
      type: "goods/addGood", // action的type需要以命名空间为前缀+reducer名称
      payload: { title }
    }),
    getList: () => ({
      type: "goods/getList", // action的type需要以命名空间为前缀+reducer名称
    })
  }
)
export default class extends Component {
  componentDidMount(){
    this.props.getList();
  }

  render() {
    console.log(this.props.loading);
    if (this.props.loading.models.goods) {
      return <div>加载中...</div>
    }

    return (
      <div className={styles.normal}>
        <h1>Page goods</h1>
        {/* 商品列表 */}
        <div>
          {this.props.goodsList.map(good => {
            return (
              <Card key={good.title}>
                <div>{good.title}</div>
              </Card>
            )
          })}
        </div>
        <div>
          {/* 添加商品 */}
          <Button onClick={() => this.props.addGood('商品' + new Date().getTime())}>添加商品</Button>
        </div>
      </div>
    );
  }
}
数据mock
  • 新建 src/mock/goods.js:
let data = [{ title: "web全栈" }, { title: "java架构师" }, { title: "python" }];

export default {
  // "method url": Object 或 Array
  // "get /api/goods": { result: data },

  // "method url": (req, res) => {}
  "get /api/goods": function(req, res) {
    setTimeout(() => {
      res.json({ result: data });
    }, 1250);
  }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值