Springboot+react+dva入门实战—前后端传值

8.jpg

前言:

本文分为2个部分:

1.前半部分为5个前置知识点,简要介绍了React中的props和state、dva中connect、dispatch方法

2.后半部分从一个项目实例入手介绍react+dva前后端传值的过程,涉及models、action、view、service。

Tips:配合官网看例子效果更好:

react官网:https://reactjs.org/

dva-github:dvajs/dva

1.React中的props和state的区别

参考:https://segmentfault.com/q/1010000008340434
在任何应用中,数据都是必不可少的。我们需要直接的改变页面上一块的区域来使得视图的刷新,或者间接地改变其他地方的数据。React的数据是自顶向下单向流动的,即从父组件到子组件中,组件的数据存储在props和state中,这两个属性有啥子区别呢?

props

React的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件。
组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是props,所以可以把props理解为从外部传入组件内部的数据。由于React是单向数据流,所以props基本上也就是从服父级组件向子组件传递的数据。

用法

假设我们现在需要实现一个列表,根据React组件化思想,我们可以把列表中的行当做一个组件,也就是有这样两个组件:和。
先看看

import Item from “./item”;
export default class ItemList extends React.Component{
  const itemList = data.map(item => );
  render(){
    return (
      {itemList}
    )
  }
}

列表的数据我们就暂时先假设是放在一个data变量中,然后通过map函数返回一个每一项都是的数组,也就是说这里其实包含了data.length个组件,数据通过在组件上自定义一个参数传递。当然,这里想传递几个自定义参数都可以。

具体是这样的:

export default class Item extends React.Component{
  render(){
    return (
      
  • {this.props.item}
  • ) } }

    在render函数中可以看出,组件内部是使用this.props来获取传递到该组件的所有数据,它是一个对象,包含了所有你对这个组件的配置,现在只包含了一个item属性,所以通过this.props.item来获取即可。

    只读性

    props经常被用作渲染组件和初始化状态,当一个组件被实例化之后,它的props是只读的,不可改变的。如果props在渲染过程中可以被改变,会导致这个组件显示的形态变得不可预测。只有通过父组件重新渲染的方式才可以把新的props传入组件中。

    默认参数

    在组件中,我们最好为props中的参数设置一个defaultProps,并且制定它的类型。比如,这样:

    Item.defaultProps = {
      item: ‘Hello Props’,
    };
    Item.propTypes = {
      item: PropTypes.string,
    };
    

    关于propTypes,可以声明为以下几种类型:

    optionalArray: PropTypes.array,

    optionalBool: PropTypes.bool,

    optionalFunc: PropTypes.func,

    optionalNumber: PropTypes.number,

    optionalObject: PropTypes.object,

    optionalString: PropTypes.string,

    optionalSymbol: PropTypes.symbol,

    注意,bool和func是简写。

    这些知识基础数据类型,还有一些复杂的,附上链接:

    https://facebook.github.io/react/docs/typechecking-with-proptypes.html

    总结

    props是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的props来重新渲染子组件,否则子组件的props以及展现形式不会改变。

    state

    state是什么呢?

    • State is similar to props, but it is private and fully controlled by the component.

    一个组件的显示形态可以由数据状态和外部参数所决定,外部参数也就是props,而数据状态就是state。

    export default class ItemList extends React.Component{
      constructor(){
        super();
        this.state = {
          itemList:‘一些数据’,
        }
      }
      render(){
        return (
          {this.state.itemList}
        )
      }
    }
    

    首先,在组件初始化的时候,通过this.state给组件设定一个初始的state,在第一次render的时候就会用这个数据来渲染组件。

    setState

    state不同于props的一点是,state是可以被改变的。不过,不可以直接通过this.state=的方式来修改,而需要通过this.setState()方法来修改state。

    比如,我们经常会通过异步操作来获取数据,我们需要在didMount阶段来执行异步操作:

    componentDidMount(){
      fetch(‘url’)
        .then(response => response.json())
        .then((data) => {
          this.setState({itemList:item});  
        }
    }
    

    当数据获取完成后,通过this.setState来修改数据状态。当我们调用this.setState方法时,React会更新组件的数据状态state,并且重新调用render方法,也就是会对组件进行重新渲染。

    注意:

    通过this.state=来初始化state,使用this.setState来修改state,constructor是唯一能够初始化的地方。setState接受一个对象或者函数作为第一个参数,只需要传入需要更新的部分即可,不需要传入整个对象,比如:

    export default class ItemList extends React.Component{
      constructor(){
        super();
        this.state = {
          name:‘axuebin’,
          age:25,
        }
      }
      componentDidMount(){
        this.setState({age:18})  
      }
    }
    

    在执行完setState之后的state应该是{name:’axuebin’,age:18}。
    setState还可以接受第二个参数,它是一个函数,会在setState调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成:

    this.setState({
      name:‘xb’
    },()=>console.log(‘setState finished’))
    

    总结

    state的主要作用是用于组件保存、控制以及修改自己的状态,它只能在constructor中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的this.setState来修改,修改state属性会导致组件的重新渲染。

    区别

    state是组件自己管理数据,控制自己的状态,可变;
    props是外部传入的数据参数,不可变;
    没有state的叫做无状态组件,有state的叫做有状态组件;
    多用props,少用state。也就是多写无状态组件。

    2.Object.assign方法

    参考:https://github.com/ruanyf/es6tutorial/blob/06fac98bb11b0b1f3fccd396d402e918ae3fc002/docs/object.md

    基本用法

    Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

    const target = { a: 1 };
    const source1 = { b: 2 };
    const source2 = { c: 3 };
    Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}
    

    Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。

    注意,如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。

    1. const target =``{ a:``1, b:``1``};
    2. const source1 =``{ b:``2, c:``2``};
    3. const source2 =``{ c:``3``};
    4. Object.assign(target, source1, source2);
    5. target // {a:1, b:2, c:3}

    如果只有一个参数,Object.assign会直接返回该参数。

    1. const obj =``{a:``1};
    2. Object.assign(obj)``=== obj // true

    注意点

    (1)浅拷贝

    Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

    1. const obj1 =``{a:``{b:``1}};
    2. const obj2 =``Object.assign({}, obj1);
    3. obj1.a.b =``2;
    4. obj2.a.b // 2

    上面代码中,源对象obj1的a属性的值是一个对象,Object.assign拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。

    (2)同名属性的替换

    对于这种嵌套的对象,一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。

    1. const target ={ a:{ b:*‘c’, d:*``*‘e’*``*}*``*}*
    2. const source =*{ a:*``*{ b:*``*‘hello’*``*}*``*}*
    3. Object.assign(target, source)
    4. // { a: { b: ‘hello’ } }

    上面代码中,target对象的a属性被source对象的a属性整个替换掉了,而不会得到{ a: { b: ‘hello’, d: ‘e’ } }的结果。这通常不是开发者想要的,需要特别小心。

    一些函数库提供Object.assign的定制版本(比如 Lodash 的_.defaultsDeep方法),可以得到深拷贝的合并。

    (3)数组的处理

    1. Object.assign可以用来处理数组,但是会把数组视为对象。
    2. Object.assign([1,2,3],[4,5])
    3. // [4, 5, 3]

    上面代码中,Object.assign把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性4覆盖了目标数组的 0 号属性1。

    (4)取值函数的处理

    Object.assign只能进行值的复制,如果要复制的值是一个取值函数,那么将求值后再复制。

    1. const source ={
    2. get foo(){return1}
    3. };
    4. const target ={};
    5. Object.assign(target, source)
    6. // { foo: 1 }

    上面代码中,source对象的foo属性是一个取值函数,Object.assign不会复制这个取值函数,只会拿到值以后,将这个值复制过去。
    如果该参数不是对象,则会先转成对象,然后返回。

    3.ES6拓展运算符

    参考:http://es6.ruanyifeng.com/#docs/object#对象的拓展运算符
    扩展运算符
    对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

    1. let z =``{ a:``3, b:``4``};
    2. let n =``{``…z };
    3. n // { a: 3, b: 4 }

    这等同于使用Object.assign方法。

    1. let aClone =``{``…a };
    2. // 等同于
    3. let aClone =``Object.assign({}, a);

    4.dva connect函数

    参考:
    https://dvajs.com/guide/introduce-class.html#connect-%E6%96%B9%E6%B3%95

    connect 方法

    connect 是一个函数,绑定 State 到 View。

    1. import``{ connect } from ‘dva’;
    2. function mapStateToProps(state)``{
    3. return``{ todos: state.todos };
    4. }
    5. connect(mapStateToProps)(App);

    connect 方法返回的也是一个 React 组件,通常称为容器组件。因为它是原始 UI 组件的容器,即在外面包了一层 State。
    connect 方法传入的第一个参数是 mapStateToProps 函数,mapStateToProps 函数会返回一个对象,用于建立 State 到 Props 的映射关系。

    5.dispatch方法

    dispatch 方法

    dispatch 是一个函数方法,用来将 Action 发送给 State。

    1. dispatch({
    2. type:``‘click-submit-button’,
    3. payload:``this.form.data
    4. })

    dispatch 方法从哪里来?被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。


    前后端传值示例:

    先看一下项目目录:

    image

    1.Index——index.js

    import dva from ‘dva’;
    import ‘./index.less’;
    import ‘./todolist.css’;
    import createLoading from ‘dva-loading’
    import ‘moment/locale/zh-cn’
    import {message, notification} from ‘antd’
    import roleManage from ‘./application/models/roleManager’
    import themeConfig from ‘./application/models/themeManager’
    // 1\. Initialize
    const app = dva({
        onError(e) {
    //     notification.error({
    //         description: e.message,
    //     })
    },
    });
    app.use(createLoading());
    // 2\. Plugins
    // dbmanager.use({});
    // 3\. Model
    app.model(require(‘./application/models/userManager’))
    ….
    app.model(require(‘./application/models/todoList’))
    // 4\. Router
    app.router(require(‘./router’))
    // 5\. Start
    app.start(‘#root’); 
    

    在index.js中定义了model,dva 提供 app.model 这个对象,所有的应用逻辑都定义在它上面。

    2.Model——userManager.js

    说直白点,model就是模型,定义了同步或者异步的操作,通常是前后端传值的操作。在model中引入service,通过调用service的具体方法完成post/get请求等前后端传值。
    userManager.js如下:

    import  as service from “../services/userManager”;
    import alert from ‘../../framework/common/alert’
    export default {
        namespace: ‘userManager’,
        state: {
            userList:[],
            roleList: [],
            spinning: true,
    },
        reducers: {
            save(state, action) {
    return {…state, …action.payload};
    },
    },
        effects: {
     queryUserList({payload}, {call, put}) {
                yield put({type: ‘save’, payload: {spinning: true}})
    const {data} = yield call(service.queryUserList)
    if (data.code == ‘0’) {
                    alert(‘查询用户’, data)
    } else {
                    yield  put({type: ‘save’, payload: {userList: data.payload}})
    }
    },
    addUser({payload}, {call, put}){
    const {data} = yield  call(service.addUser, payload)
    if (data.code == ‘0’) {
                    alert(‘添加人员’, data)
    }
                yield  put({type: ‘queryUserList’})
    },
    update({payload}, {call, put}){
    const {data} = yield  call(service.update, payload)
    if (data.code == ‘0’) {
                    alert(‘修改人员’, data)
    }
                yield  put({type: ‘queryUserList’})
    },
    deleteUser({payload}, {call, put}){
    const {data} = yield  call(service.deleteUser, payload)
    if (data.code == ‘0’) {
                    alert(‘修改人员’, data)
    }
                yield  put({type: ‘queryUserList’})
    },
     queryAllRole({payload}, {call, put}) {
    const {data} = yield call(service.queryAllRole)
    if (data.code == ‘0’) {
                    alert(‘查询角色’, data)
    }
                yield put({type: ‘save’, payload: {roleList: data.payload}})
    },
    },
        subscriptions: {
            setup({dispatch, history}) {
    },
    },
    } 
    

    Model 对象的属性:

    namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
    state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
    reducers: Action 处理器,处理同步动作,用来算出最新的 State
    effects:Action 处理器,处理异步动作
    参考:https://dvajs.com/guide/introduce-class.html

    3.Service——userManager.js

    Service类似Java中的Service,不过这里不用写接口,直接就是实现类了,在Service中直接调用方法和后端进行数据交互。

    import axios from ‘axios’
    export async function queryUserList() {
    return axios.get(‘userManager/queryUserList’)
    }
    export async function addUser(payload) {
    return axios.post(‘userManager/addUser’,payload)
    }
    export async function update(payload) {
    return axios.post(‘userManager/update’,payload)
    }
    export async function deleteUser(payload) {
    return axios.post(‘userManager/deleteUser’,payload)
    }
    export async function queryAllRole() {
    return axios.post(‘/role/queryAllRole’)
    } 
    

    4.Router——router.js

    import React from “react”;
    import {Route, Router, IndexRoute} from “dva/router”
    import UserManager from ‘./application/views/UserManager/index’
    import Todolist from‘./application/views/TodoList’
    function RouterConfig({history, app}) {
    return (
    
    
    
    
    //…
    
    
    
    )
    }
    export default RouterConfig 
    

    router是路由,router.js文件定义了路径path和对应的跳转页面,类似Java中的Controller。如:
    Route path=”userManager” component={UserManager}/>
    component是组件的意思,即相应path需要跳转到的页面。userManager组件在开头已经通过import引入了:
    import UserManager from ‘./application/views/UserManager/index’
    当访问http://localhost:8080/userManager 时会自动找到UserManager组件,(位于application/views/UserManager文件夹下的index.js文件)然后将其渲染成页面。
    PS:到这里其实我们知道接下来要来到这个userManager组件所在的页面了。那之前1.2.3.步中的index和model和service有什么用呢?暂时没有用,因为这些都是为了后面前后台数据交互(传值)用的。

    5.View——index.js

    View顾名思义是视图层,视图层就是页面,在react中即组件,通常一个页面组件是顶层组件,也就是一个view,一个页面组件中通常会包裹其他各种小组件。截图如下:

    image

    里面主要包含各种react周期函数和自定义的各种方法,最后通过render()来渲染出页面的html。

    以上就是页面的流通过程,下面结合具体页面操作来过一遍前后端传参过程。
    1.进入http://localhost:8080/userManager 页面:

    image

    其中import头部如下:

    image

    点击新增按钮,对应View——index.js中render()方法中如下元素:

    1. type=“primary” style={{borderRadius:``15, width:``100}}>``新增``

    react中一切皆组件,这个button也是一个组件,里面包含onClick、type、style属性。这里的button并不是自定义的组件,而是是引用自antd的开源组件
    PS: antd是蚂蚁金服开源的基于react的前端UI组件库具体可以参考官网:https://ant.design/components/icon-cn/)
    button常用属性如下:

    image

    2.onClick触发箭头函数→this.setState({modalDisplay: “add”})
    this.setState是react中的语法,作用是改变当前组件的state。这时,看一下整个页面初始化构造器中其实是在state中定义了modalDisplay 的:

    constructor(props) {
        super(props)
    this.state = {
          userId: window.sessionStorage.getItem(“userId”),
          modalDisplay: “none”,
          selectUser: null,
          modalVisible: false
    }
    } 
    

    通过给modalDisplay赋值,modalDisplay从null变为了”add”,组件的state改变会触发相应的元素重新渲染,那么对应的就是页面组件中的Modal组件:

     this.handleCancel() }
                   cancelText=“关闭”
                   okText=“确定”
                   onOk={() => this.editUser()}> 
    

    Modal和button组件一样,都是antd组件,当此组件捕获到整个state改变后,(modalDisplay从none变为”add”),Modal的visible属性变为true,故此Modal被渲染了出来,效果是如下弹框:

    image

    3.前面只是铺垫,只是前端页面的点击和渲染,还没涉及到前后端数据交互,从Modal组件上点击【确定】时,正式开始前后端传参过程。Modal组件上点击【确定】调用组件的editUser()方法。

    /编辑用户/
      editUser() {
    const {selectUser} = this.state
    const {form, dispatch} = this.props
    const {validateFields, resetFields} = form
        validateFields((errors, values) => {
    if (errors) {
    return
    }
    if (!selectUser) {
            values.userId = this.state.userId
            dispatch({type: ‘userManager/addUser’, payload: values})
            resetFields()
    this.setState({modalDisplay: “none”})
    } else {
            values.userId = this.state.userId
            dispatch({type: ‘userManager/update’, payload: values})
            resetFields()
    this.setState({modalDisplay: “none”, selectUser: null})
    }
    })
    } 
    

    selectUser是页面组件初始化constructor中定义的state属性,初始为null。在editUser()中先判断selectUser,如果为空则执行:
    dispatch({type: ‘userManager/update’, payload: values})
    不为空则执行:
    dispatch({type: ‘userManager/addUser’, payload: values})
    这里,dispatch是dva中定义的函数,用来将 Action 发送给 State。Action 是一个普通 javascript对象,它是改变 State 的唯一途径。一切触发state改变的行为都可以叫Action,此处Action即为:{type: ‘userManager/update’, payload: values}
    PS:被 connect 的 Component 会自动在 props 中拥有 dispatch 方法。这里页面组件在开始已经做了connect:

    1. @connect(state =>``Object.assign({}, state.userManager,``{loading: state.loading.models.userManager}))

    这个connect看起来挺复杂的,用到了箭头函数、Object.assign()方法。但是其原理很简单,就是绑定state到view。此处即绑定当前组件的state到models下的userManager.js(view)
    4.此处以dispatch({type: ‘userManager/addUser’, payload: values})为例看一下添加用户的前后端交互流程。
    state改变后触发contact绑定过的models中userManager.js中addUser方法:

    effects: {
    addUser({payload}, {call, put}){
    const {data} = yield  call(service.addUser, payload)
    if (data.code == ‘0’) {
                    alert(‘添加人员’, data)
    }
                yield  put({type: ‘queryUserList’})
    },
    …
    } 
    

    在这里call和put方法都是effect内部常用的处理函数。
    call:执行异步函数,这里通过call方法调用service来实现向后台传值,
    put:发出一个 Action,类似于 dispatch,这里通过put一个Action:{type: ‘queryUserList’}来改变前台state状态。
    5.service.addUse
    这里就是前台页面向后端传值的最后一个步骤.

    image

    你可能感兴趣的:(Springboot+react+dva入门实战—前后端传值)