小工具      在线工具  汉语词典  css  js  c++  java

React+DVA开发实践

JavaScript 额外说明

收录于:43天前

原文链接

文档概述

本文档在前面章节简单的介绍了React和其相关的一系列技术,最后章节介绍了React+Dva开发的整套过程和基本原理,也就是将一系列框架整合的结果。

文件结构

本文档划分为以下章节,前面几个章节是知识储备,最后章节是项目实践
  • 反应
  • 通量
  • 最终版本
  • React-Router
  • 路霸
  • 蚂蚁设计
  • 数字视场
  • 项目实践

绑定属性

本文中蓝色字体为超链接
本文中红色字体为特别注意内容

反应

React是近期非常火热的一个前端开发框架,当然也有很多人认为它不是一个框架,因为它仅仅是作为MVC模式中的V层用来构建UI。在整个Web应用的MVC架构中,你可以将React看作为视图层,并且是一个高效的视图。React提供了和以往不一样的方式来看待视图,它以组件开发为基础。 对React应用而言,你需要分割你的页面,使其成为一个个的组件。也就是说,你的 应用是由这些组件组合而成的。你可以通过分割组件的方式去开发复杂的页面或某个功能区块,并且组件是可以 被复用的。这个过程大概类似于用乐高积木去瓶装不同的物体。我们称这种编程方式称为组件驱动开发。作为Facebook推出的一个JS库,React除了技术本身,人们更看重它的其实是它那种独特的开发思想,并在此基础上衍生出了一些列相关技术。React 通过对虚拟 DOM 中的微操作来实对现实际 DOM 的局部更新,提高性能。其组件的模块化开发提高了代码的可维护性,单向数据流的特点,让每个模块根据数据量自动更新。

JSX 语法

JSX 本质上是一种新语言,但它被设计为 JavaScript 的扩展,因此它的很多语法与 JavaScript 相同。同时,它还配备了 JSX Transform 工具,可以将 JSX 编译为原生 JavaScript。那么这样做有什么好处呢?当然,首要任务是让你更容易编码,否则每次想到都要React.createElement就太疯狂了!其次,另一个好处是它可以让你像CoffeeScript一样编写ES6等语法,并且最终会被翻译成浏览器兼容的语法。

虚拟DOM

在传统的 Web 应用中,我们往往会把数据的变化实时地更新到用户界面中,于是每次数据的微小变动都会引起 DOM 树的重新渲染。如果当前 DOM 结构较为复杂,频繁的操作很可能会引发性能问题。React 为了解决这个问题,引入了虚拟 DOM 技术 
虚拟 DOM 是一个 JavaScript 的树形结构,包含了 React 元素和模块。组件的 DOM 结构就是映射到对应的虚拟 DOM 上,React 通过渲染虚拟 DOM 到浏览器,使得用户界面得以显示。与此同时,React 在虚拟的 DOM 上实现了一个 diff 算法,当要更新组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以在 React 中,当页面发生变化时实际上不是真的渲染整个 DOM 树。

组件概念

虚拟DOM不仅带来了简单的UI开发逻辑,同时也带来了组件化开发的思想,所谓组件,即封装起来的具有独立功能的UI部 件。React推荐以组件的方式去重新思考UI构成,将UI上每一个功能相对独立的模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成大的组件, 最终完成整体UI的构建。 
如果说MVC的思想让你做到视图-数据-控制器的分离,那么组件化的思考方式则是带来了UI功能模块之间的分离。对于MVC开发模式来说,开发者将三者定义成不同的类,实现了表现,数据,控制的分离。开发者更多的是从技术的角度来对UI进行拆分,实现松耦合。对于React而言,则完全是一个新的思路,开发者从功能的角度出发,将UI分成不同的组件,每个组件都独立封装。 
在React中,按照界面模块自然划分的方式来组织和编写的代码,整个UI是一个通过小组件构成的大组件,每个组件只关心自己部分的逻辑,彼此独立。

组件属性

  • 可组合:组件易于与其他组件一起使用,或嵌套在另一个组件中。如果一个组件在内部创建了另一个组件,则称父组件拥有它所创建的子组件。通过该功能,可以将一个复杂的UI拆分为多个简单的UI组件;
  • 可复用:各组件功能独立,可用于多种UI场景;
  • 可维护:每个widget只包含自己的逻辑,更容易理解和维护;
  • 可测试:每个组件都是独立的,因此易于测试。

数据流道具、状态

在React中,数据流从父节点到子节点是单向的,因此组件简单且易于掌握。它们只需要从父节点获取 props 即可进行渲染。如果顶级组件的 prop 发生变化,React 将递归遍历整个组件树并重新渲染使用该属性的所有组件。 React 组件内部也有自己的状态,只能在组件内部修改。 React 组件本身非常简单。您可以将它们视为接受 props 和 state 作为参数并返回虚拟 DOM 表示的函数。简单来说,React使用props形成单向数据流,并使用state来更新界面。

  • 数据流与props 
    React中的数据流是单向的,只会从父组件传递到子组件。属性props(properties)是父子组件间进行状态传递的接口,React会向下遍历整个组件树,并重新渲染使用这个属性的组件。
  • 组件内部状态state 
    props可以理解为父组件与子组件间的状态传递,而React的组件都有自己的状态,这个内部状态使用state表示。state是组建的属性,主要用来存储组件自身需要的数据。它是可以改变的,它的每次改变都会引起组件的更新,这也是ReactJS中的关键点之一。每次数据的更新都是通过修改state属性的值,然后ReactJS内部会监听state属性的变化,一旦发生变化,就会主动出发组件的render方法来更新DOM结构。简单来讲:用户界面随着state的变化而变化。

    哪些组件应该有状态? 
    大部分组件的工作应该是从props里取数据并渲染出来,但有时需要对用户输入、服务器请求或者时间变化等作出响应,这时才需要state。组件应该尽可能的无状态化,这样能隔离state,把它放到最合理的地方(Redux做的就是这个事情?),也能减少冗余并易于解释程序运作过程。 
    常用的模式就是创建多个只负责渲染数据的无状态(stateless)组件,在他们的上层创建一个有状态(stateful)组件并把它的状态通过props传给子级。有状态的组件封装了所有的用户交互逻辑,而这些无状态组件只负责声明式地渲染数。 
    哪些应该用作状态? 
    state应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据.这中数据一般很小且能被JSON序列化。当创建一个状态化的组件的时候,应该保持数据的精简,然后存入this.state.在render()中在根据state来计算需要的其他数据.因为如果在state里添加冗余数据或计算所得数据,经常需要手动保持数据同步。 
    这些不应该被用作状态吗? 
    this.state应该仅包括能表示用户界面状态所需要的最少数据,因此不应该包括: 计算所得数据; React组件:在render()里使用props和state来创建它; 
    基于props的重复数据:尽可能保持用props来做作为唯一的数据来源,把props保存到state中的有效的场景是需要知道它以前的值得时候,因为未来的props可能会变化。

组件创建

React提供3种方法创建组件,具体的3种方式: 
1) 函数方式:通过定义函数创建无状态组件 
2) ES6方式:通过extends React.Component创建组件 
3) ES5原生方式:通过extend React.createClass定义的组件 
无状态组件 
创建无状态组件形式是从React 0.14版本开始出现的。它是为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到要改变state状态的操作。在大部分React代码中,大多数组件被写成无状态的组件,通过简单组合可以构建成其他的组件等;这种通过多个简单然后合并成一个大应用的设计模式被提倡。

const QaQuestion =({props})=>{
  return(
    <div> hello world </div>
  )
}

上面的代码创建了一个名为 QaQuestion 的无状态组件。该组件接收一个 props 参数,并且只包含一个 div。无状态组件的创建形式使得代码更具可读性,减少了大量冗余代码,大大增强了编写组件的便利性。此外,无状态组件还有以下显着特点:

  • 组件不会被实例化,整体渲染性能得到提升 
    由于是无状态组件,所以无状态组件就不会在有组件实例化的过程,无实例化过程也就不需要分配多余的内存,从而性能得到一定的提升。
  • 组件不能访问this对象 
    无状态组件由于没有实例化过程,所以无法访问组件this中的对象,例如:this.ref、this.state等均不能访问。若想访问就不能使用这种形式来创建组件
  • 组件无法访问生命周期的方法 
    因为无状态组件是不需要组件生命周期管理和状态管理,所以底层实现这种形式的组件时是不会实现组件的生命周期方法。所以无状态组件是不能参与组件的各个生命周期管理的。
  • 无状态组件只能访问输入 props,相同的 props 会得到相同的渲染结果。

在大型项目中,鼓励尽可能简单地编写无状态组件,以分解其他大型组件。未来React还会对无状态组件在无意义的检查、内存分配等方面进行一系列的优化,所以尽可能使用无状态组件。

由原生 React.createClass 方法创建 
React.createClass是react刚开始推荐的创建组件的方式,现在已经不推荐使用:

var ES5Compant = React.createClass({
    render: function() {
     
        return ( 
            <div> something: </div> ); } });

通过 React.createClass 方法和 extends React.Component 方法创建的组件是有状态组件。不过,随着React的发展,这种通过React.createClass创建组件的方法也暴露出了一些问题,并且在React的未来版本中,将不再支持这种方式创建组件,所以不推荐这种方法。

通过扩展 React.Components 创建 
React.Component是以ES6的形式来创建react的组件的,是React目前极为推荐的创建有状态组件的方式,最终会取代React.createClass形式。

class ES6Compant extends React.Component {
     
    constructor(props) { 
        super(props); 
        // 设置 initial state 
        this.state = { 
            text: props.initialValue || 'placeholder' 
        }; 
    }
    render() { 
        return ( <div> something </div> ); 
    } 
}

React.createClass创建的组件的状态是通过getInitialState方法配置的; React.Component 创建的组件的状态在构造函数中声明,就像初始化组件属性一样。

组件生命周期

在组件的整个生命周期中,随着该组件的props或者state发生改变,它的DOM表现也将有相应的变化,一个组件就是一个状态机:对于特定的输入,它总会返回一致的输出。 React为每个组件提供了生命周期钩子函数去响应不同的时刻,组件的生命周期分为三个部分:(1)实例化;(2)存在期;(3)销毁&清理期。 
这里写图片描述 
getInitialState: 初始化组件的state的值,其返回值会赋值给组件的this.state属性。对于组件的每个实例来说,这个方法的调用次数有且只有一次。与getDefaultProps方法不同的是,每次实例创建时该方法都会被调用一次。

componentWillMount :此方法会在完成首次渲染之前被调用。这也是在render方法调用前可以修改组件state的最后一次机会。 
render :生成页面需要的虚拟DOM结构,用来表示组件的输出。Render需要满足:(1)只能通过this.props和this.state访问数据; 
(2)可以返回null、false或者任何React组件; 
(3)只能出现一个顶级组件; 
(4)必需纯净,意味着不能改变组件的状态或者修改DOM的输出。

componentDidMount:该方法在成功调用render方法并且已经渲染出真实的DOM之后发生。在这个函数内部,可以通过this.getDOMNode()获取当前组件的节点。然后,您可以像在 Web 开发中一样操作其中的 DOM 元素。

componentWillReceiveProps:任何时候,组件的 props 都可以通过父组件更改。当组件收到新的 props 时(与这里的 state 不同),这个函数就会被触发,我们也将有机会改变 props 对象并更新 state。

shouldComponentUpdate:该方法用于拦截新的 props 和 state,然后开发人员可以根据自己的逻辑决定是否更新 render 以使其更快。

componentWillUpdate:与 componentWillMount 方法类似,该方法在组件接收新的 props 或状态渲染之前调用。但状态和道具不能在此方法中更新。

componentDidUpdate:与componentDidMount类似,更新渲染的DOM。

componentWillUnmount:该方法将在组件被移除之前调用。在 componentDidMount 方法中添加的所有任务都需要在该方法中撤消,例如创建的计时器或添加的事件监听器等。

通量

Flux 是一种应用架构,或者说是一种思想或模式而不是一个正式的框架,它跟 React 本身没什么关系,它可以用在 React 上,也可以用在别的框架上。Flux 在 React 中主要用来统一管理引起 state 变化的情况。Flux 维护着一个或者多个叫做 Store 的变量,就像 MVC 里面的 Model,里面存放着应用用到的所有数据,当一个事件触发时 ,Flux 对事件进行处理,对 Store 进行更新,当 Store 发生变化时,通常是由应用的根组件(也叫 controller view)去获取最新的 store,然后更新 state,之后利用 React 单向数据流的特点一层层将新的 state 向下传递实现 view 的更新。这里的 controller view 可以有多个也可以不是根组件,但是这样数据流维护起来就比较麻烦。

这里写图片描述 
Dispatcher就像一个中心枢纽所有数据的流动都要通过这里。Action来源于用户与views的交互行为,Action触发Dispatcher。Dipatcher分发这个事件给对应的Store(通过之前注册的回调函数callback)。Store在修改State后触发一个”change”事件通知controller-views数据发生变化了。controller-views监听这些”change”事件并且从stores暴露的函数中获取(新)数据,然后调用自己的setState()方法,rerender自己和它的子组件。

  • Dispatcher 
    事Dispatcher是Flux应用中管理所有数据流的中心枢纽。它本质上就是一些Store回调函数的注册器,它本身没有其他逻辑 - 
    只是提供了把Action分发给Store的机制。dispatcher根据action 
    type调用对应的回调函数。每一个Store都在Dispatcher注册(AppDispatcher.register)并提供回调函数。随着应用的发展,Dispatcher会变得越来越重要。例如Dispatcher可以用来管理Stores之间的依赖关系,通过特定的顺序来调用注册了的回调函数就可以办到。Stores可以等到其他Stores完成更新再进行自己的更新操作。
  • Store 
    Store负责封装应用的业务逻辑跟数据的交互,包含应用所有的数据,是应用中唯一的数据发生变更的地方。Store中没有赋值接口,所有数据变更都是由dispatcher发送到store,新的数据随着Store触发的change事件传回view。Store对外只暴露getter,不允许提供setter,禁止在任何地方直接操作Store。
  • Views和Controller-Views React在View(MVC)层提供了可组合的可自由重新渲染的Views, 
    在嵌套的views结构顶部, 一个特别的view监听着stores广播的事件, 
    我们管这种view叫controller-view。在controller-view中我们完成这样的操作::从stores中获取数据并且传递这些数据的到它的子代中. 
    我们总有一个这样的controller-view控制页面的某一部分。 
    当controller-view接受到store广播的事件,它首先从store的公共getter方法中获取它需要的新数据,然后调起setState()或者forceUpdate()方法,那么它和它所有子代的render()方法都会运行。 
    我们常常把整个store的state放在一个对象里面传递到子代中,让子代选择自己需要的东西。这样除了可以在层级结构顶层保持控制(controller)行为因此尽可能保证子代views的单一功能外,还可以减少我们需要管理的属性(props)的数目。 
    有时候我们可能需要在层级结构的某一层建立另外的一些controller-view使一些组件能简单些。这样可以帮助我们更好地去封装层级上的与特定的数据有关联的一些模块。请注意,在不是顶层建立一个controller-view会破坏单项数据流这个原则,因为有可能会存在数据入口的冲突。在做这样的决定之前,我们可以衡量一下得到一个简单一点的组件和多重数据流多个数据更新入口孰轻孰重。多重数据流会有一些副作用: 
    React的render()方法会因为不同的controller-view的数据更新而多次被处罚, 会增加debug的难度。

最终版本

Redux 的功能与 Flux 相同。可以看作是Flux的一种实现,但还是有一点不同。

  • Redux 只有一个 store。 Flux中会有多个store来存储应用数据,更新逻辑将在store中执行。当store发生变化时,controller-view会收到通知来更新自己的数据。 Redux会将每个store整合成一个完整的store,应用程序的完整状态可以从这个store中导出。同时Redux中更新的逻辑并不是在store中执行而是放在reducer中。

  • 没有 Dispatcher。 Redux 中没有 Dispatcher 的概念,它使用 reducer 来进行事件的处理,reducer 是一个纯函数,这个函数被表述为 (previousState, action) => newState ,它根据应用的状态和当前的 action 推导出新的 state。Redux 中有多个 reducer,每个 reducer 负责维护应用整体 state 树中的某一部分,多个 reducer 可以通过 combineReducers 方法合成一个根reducer,这个根reducer负责维护完整的 state,当一个 action 被发出,store 会调用 dispatch 方法向某个特定的 reducer 传递该 action,reducer 收到 action 之后执行对应的更新逻辑然后返回一个新的 state,state 的更新最终会传递到根reducer处,返回一个全新的完整的 state,然后传递给 view。 
    Redux 和 Flux 之间最大的区别就是对 store/reducer 的抽象,Flux 中 store 是各自为战的,每个 store 只对对应的 controller-view 负责,每次更新都只通知对应的 controller-view;而 Redux 中各子 reducer 都是由根reducer统一管理的,每个子reducer的变化都要经过根reducer的整合。用图表示的话可以像这样: 
    Flux 中的 store : 
    这里写图片描述 
    Redux 中的 store(或者叫 reducer) 
    这里写图片描述

React-Router

React-Router 是一个为 React 设计的强大的路由库。可以帮助我们快速的实现路由功能,包括 URL 和 React components 之间的同步映射关系。

React Router 使用教程

  • 前端路由 前端的路由和后端的路由在实现技术上不一样,但是原理都是一样的。在 HTML5 的 history API出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器,它的 URI 规则中需要带上 #。例如:http://localhost:8000/#/登录 Web 服务并不会解析 hash,也就是说 # 后的内容 Web服务都会自动忽略,但是 JavaScript 是可以通过 window.location.hash读取到的,读取到路径加以解析之后就可以响应不同路径的逻辑处理。 history 是 HTML5 才有的新 API,可以用来操作浏览器的session history (会话历史)。基于 history 来实现的路由可以不需要#,例如localhost:8080/login

  • 前端路由 
    先看一段配置代码

    component={qaBasic}>
       <Route path="/qaBasic/qaGuide" component={qaGuide}/>
       <Route path="/qa/qaQuestion" component={qaQuestion}/>    <Route path="/qa/qaAskBasic" component={qaAskBasic}/>   </Route> </Router>

    Router 组件本身只是一个容器。真正的路由必须通过Route组件来定义。 Path对应的是访问路径,Component是路径对应的组件。例如:在浏览器中访问/qaBasic时,会加载qaBasic组件。当然,还有组件嵌套,即一个Route中包含另一个子Route,也就是说访问子组件时,会先加载父组件,然后再加载子组件中的组件父组件。

路霸

roadhog 是一个 cli 工具,提供 server、 build 和 test 三个命令,分别用于本地调试和构建,并且提供了特别易用的 mock 功能。命令行体验和 create-react-app 一致,配置略有不同,比如默认开启 css modules,然后还提供了 JSON 格式的配置方式。 
roadhog配置详解

蚂蚁设计

Ant design是蚂蚁金服出品的一款前端UI librar,提供了丰富的React组件。 
ant design 组件库

数字视场

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装,没有引入任何新概念,全部代码不到 100 行。( Inspired by elm and choo. )dva 是 框架,不是 library,类似 emberjs,会很明确地告诉你每个部件应该怎么写,这对于团队而言,会更可控。另外,除了 react 和 react-dom 是 peerDependencies 以外,dva 封装了所有其他依赖。dva 实现上尽量不创建新语法,而是用依赖库本身的语法,比如 router 的定义还是用 react-router 的 JSX 语法的方式。

dva简介

项目实践

本章节介绍windows系统下react+dva构建项目实现过程,将前面介绍的知识点进行整合,主要包括:项目的搭建、基本配置、目录规划、路由配置、前端通过调用后台restful接口获取数据、react组件间的数据传递等。

创建项目

这里通过dva来快速新建一个项目,当然在次之前需要提前准备好node环境 
1) 安装node

2)安装dva-cli

  • 安装:npm install dva-cli -g
  • 检测:dva-v

3)使用dva创建项目

  • dva new PORTAL // 会创建PORTAL目录,并在该目录下生成一些基本配置文件

4)启动应用程序

  • 在PORTAL目录下,执行命令npm start

基本配置

在项目创建成功之后,会在项目下看到一些基础配置文件,这也是一点通过dva来构建项目的方便之处。一般可以看到以下几个配置文件:.eslintrc、.editorconfig、.roadhogrc、.roadhogrc.mock.js、package.json,这里简单了解两个配置文件:roadhogrc和package.json。基于npm模式开发的时候,和以前那样纯粹的写js代码不同,因为这是面向模块化的前端开发。有关于前端模块化开发,这里提供一个介绍文档。

基于npm模式的前端开发

package.json配置文件与maven中的pom.xml文件非常相似。虽然它还有其他用途,但很多时候它的功能是作为管理模块,例如:

"dependencies": {
 "antd": "^2.10.0",
 "babel-plugin-import": "^1.1.1",
 "babel-runtime": "^6.9.2",
 "dva": "^1.2.1",
 "echarts": "^3.5.4",
  "qs": "^6.4.0",
 "react": "^15.4.0",
 "react-bootstrap": "^0.31.0",
  "react-dom": "^15.4.0"
 },

将项目中需要用到的模块添加进package.json文件,然后执行 npm install 就可以将这些模块从npm库下载到本地。 
.roadhogrc配置文件里面的内容是一个json对象,是对roadhog模块的配置

  "entry": "src/desktop/index.js",
  "proxy": {
    "/api": {
      "target": "http://localhost:8080/api/",
      "changeOrigin": true,
      "pathRewrite": { "^/api" : "" }
    },
    "/oauth": {
      "target": "http://localhost:8080/oauth/",
      "changeOrigin": true,
      "pathRewrite": { "^/oauth" : "" }
    }
  },

上面代码中,entry指定了整个项目的入口文件; proxy 设置代理,意味着所有以 api 开头的请求都会被配置。

目录规划

前端应用越来越复杂,也越来越规范,在前后端分离的系统中,前端实际上已经控制了MVC模式中的Controller和View层,而后端仅仅是作为M层提供数据。因此,在前端应用开发过程中,特别是基于React这套前端框架的应用中,目录规划显得十分重要。在利用dva开发前端构建react的应用中,主要划分为以下几个目录:components、container、models、routes、services、utils、styles 以下是项目目录截图: 
这里写图片描述

  • 组件:最基本的组件。这里只存放最基本的UI组件。这些组件接收外部传递过来的参数(数据),并将这些数据渲染到界面上。根据传入的参数不同,界面渲染也不同。
  • container:contatiner负责将数据的组件进行连接,相当于将compontent组件和store里面的数据进行包装,生成一个新的有数据的组件。然后,在router.js配置文件中引用container中的组件。 
    routers:router目录其实和container目录基本一样,只是在利用dva开发的应用中叫router,在不是利用dva开发的应用中叫container而已,两者有一个即可。
  • models:model是dva中的一个重要概念,也可以看作是前端中的数据层。在我的理解里,dva将model以 
    namespace作为唯一标识进行区分,然后将所有model的数据存储到redux 
    中的store里面。在引用的时候,通过各个model的namespace进行引用。Model,是一个处理数据的地方,在model里面调用service层获取数据。 
    services:services负责向后台请求数据,在services里调用后台提供的api获取数据。
  • utils:工具类目录,比如常用的后台接口请求工具类。
  • styles:存储css或less样式文件。
  • Constants.js:在里面定义一些常用的常量。
  • router.js:为整个应用程序配置路由。
  • index.js:整个应用程序的入口文件,dva与其他框架略有不同。

路由配置

路由配置主要是控制浏览器上界面的跳转。这里引用的框架是react-router。在 router.js 中为整个应用程序配置路由配置。主要需要注意的是,容器组件一般都是在router.js中引用的。通过配置,将路径与浏览器上要加载的相应组件进行匹配,然后通过window.location.hash或'routerRedux'组件在路由之间跳转。

import React from 'react';
import { Router, Route, IndexRoute, history} from 'dva/router';
import qaBasic from './container/qa/qaBasic';
import qaGuide from './container/qa/qaGuide';
import qaQuestion from './container/qa/qaQuestion';
import qaAskBasic from './container/qa/qaAskBasic';
function RouterConfig({ history }) {
    
  return (
    <Router history={history}>
      <Route path='/qaBasic' component={qaBasic}>
        <Route path="/qa/qaGuide" component={qaGuide}/>
      </Route>
    </Router>
  )
}
export default RouterConfig;

以上是最基本的路由配置。 Path对应浏览器地址栏中的路径,Component是访问该路径时将加载到界面中的组件。这里也使用了路由器嵌套,即一个路由器嵌套在另一个路由器内部。这样的话,当访问子路由器对应的路径时,会先加载父路由器对应的组件,然后再在父组件内部加载子组件。与router对应的组件。

前后端交互

在前后端分离的项目中,前后端的数据交互式通过在前端应用中调用后端提供的restful接口获取数据。在dva构建的前端应用中,标准的前后端交互大概是这个流程: 
1) 新建一个model 
model是dva中非常重要的一个概念,dva中的model实际上类似于封装了redux里面的action和reducer,并给每个model提供一个namespace交于strore管理。这样,在外部引用的时候,可以直接获取到model对应的namespace,然后根据namespace获取数据。 
新建一个model主要注意以下几个细节:

  • model需要在 index.js 里面声明 
    app.model(require(‘./models/qa));

  • 模型需要有属性值命名空间

  • 外部使用model里面的方法值时需要通过namespace 
    namespace/方法名
import dva from 'dva';
import * as service from '../services/qa';
export default {
namespace: 'qa',
state: {
     questionList:[],
},
subscriptions: {
    setup ({ dispatch }) {
        dispatch({ type: 'fetchGuide',payload:{}});
    },
},
effects: {
    *fetchGuide({ payload:{guidelineId}},{ call, put }) {                                                                           const {rows}  = yield call(service.fetchGuide, {guidelineId});
    yield put({
    type: 'guideSave',
    payload: { guideList: rows}  
    });    
},
reducers: {
guideSave(state, { payload: { guideList, breadcrumb} } ) {
    return { ...state, guideList};
},
},
};

上面创建了一个名为“qa”的新模型,并在effects中添加了fetchGuide方法,在reducers中添加了guideSave方法。

Subscriptions中的内容表示在项目启动、模型加载时执行。调度({ type: 'fetchGuide', load: {}});相当于在项目启动时调用fetchGuide。方法;

effects中的put方法会调用reducers中的方法,根据方法中参数类型的值找到reducers中的方法,并执行。这个过程的原理就是redux中dispatch一个action的过程。

减速器中的方法负责更改存储中的值。事实上,这是改变商店中的值的唯一方法。

2) 新建一个service 
在上面的model中,可以发现有这样代码: 
import * as service from ‘../services/qa’; 
const {rows} = yield call(service.fetchGuide, {guidelineId}); 
其中yeild 是ES6中的关键字,表示以同步的写法来实现异步操作。可以发现,这里引入了一个service目录下的qa.js文件,并调用了该文件中的fetchGuide方法

import request from '../utils/request';
import {stringify} from 'qs';
const headers={
     'Content-Type': 'application/x-www-form-urlencoded;utf-8',
};
// 查看指引
export function fetchGuide(body={}) {
    
body.access_token = localStorage.access_token;
 return request(`/api/qa/guide`,{
    method: 'POST',
    headers: headers,
    body: stringify(body)
});
}

可以看到,这个文件比较简单: 
首先从utils目录学引入了一个工具类,该工具类主要用来请求后端数据。就是 
一个工具类而已,传入两个参数,一个是后台提供的restful API地址,一个是参 
数。然后得到后台返回的数据,这就是这个工具类的主要用途。然后再service的 
fetchGuide方法里面,传入参数进行调用,并最终返回后台数据。也就是说,在 
model里面调用service,可以获取后台的数据,然后保存到store中。

3) 配置代理 
这一部分的内容我其实并不太清楚它的前因后果。目前知道的做法是这样的,在我们的.roadhogrc配置文件中,添加以下内容:

  "proxy": {
    "/api": {
      "target": "http://localhost:8080/api/",
      "changeOrigin": true,
      "pathRewrite": { "^/api" : "" }
    },
    "/oauth": {
      "target": "http://localhost:8080/oauth/",
      "changeOrigin": true,
      "pathRewrite": { "^/oauth" : "" }
    }
  },

以上内容表示在前端请求以‘api’为前缀的api的时候,会使用代理:怎么说呢,就相当于在请求/api/qa/guide 这个路径的时候,最后实际上请求的路径会是http://localhost:8080/api/ qa/guide,这样一方面方便了我们配置,在改变ip的时候只需要在配置文件里面改革ip就可以了,很方便。但不是很了解这个到底是怎么一个流程?还有,使用这种方式会自动解决js跨域的问题吗?因为在一般情况下,js跨域问题是需要去解决的,那这种方式呢?还不是很懂。 
因此,dva中的前后端交互主要就是以下流程: 
这里写图片描述

组件数据流

前一小节讲解了在dva中的前后端交互流程,在获取到数据之后,接下来面临的一个问题就是怎么将数据传递到组件上了。 
我们知道,react是自上而下的单向数据流,也就是从父组件传递到子组件,而不能从子组件传递到父组件。那么当我们需要将子组件的数据传递到父组件时,该怎么办呢?一种方法是使用回调函数,当发生某个操作时执行回调函数改变state然后重新渲染界面。还有一种方法是使用第三方框架。Dva中就包含了一个这样的框架:redux 
在redux中,通过store管理所有的state,dva只是将几个框架进行整合,根本的东西其实根本没有一丝改变,所以dva中model里面的那些数据其实都是存储在store里面的。Model下的namespace,就相当于是store下的一个个属性。理解清楚了这个,那么给组件传递数据的流程也就清楚了。 
1) 在container组件中,通过redux中的connect获取store里面的数据 
这就是redux那一套的标准写法。首先就是在container组件里面引用components组件,然后将store下的数据传递到components组件上

import { connect } from 'dva';
import QaQuestion from '../../components/qa/QaQuestion';
const qaQuestion = ({qa})=>{
    return (
        <QaQuestion props={qa.questionList}/>
    );
}
export default connect(({ qa }) => ({ qa }))(qaQuestion); 

上面是一个容器组件。当然,上面的写法有点多余:const qaQuestion。这里实际生成的是一个Components组件,然后将QaQuestion Components组件封装到qaQuestion组件中。这有点多余。但这并不影响我们分析问题。 Connect是redux提供的一个功能。它的作用是连接数据和组件,也就是所谓的将数据传递给组件组件。这里我们传递了一个qa参数,它实际上是一个命名空间名为qa的模型。当然,数据最终是存储在store中的。也就是说,通过connect函数,我们可以直接获取store中的数据(模型也在store中);然后在qaQuestion组件上,我们接收一个参数,这个参数是从connect高阶函数中取出来的参数,然后我们再将qa下的questionList值传递给QaQuestion组件,参数命名为props,这样我们就可以直接使用QaQuestion组件中的props(其值为qa.questionList)参数。

2)在components组件中使用传入的数据

const QaQuestion =({props})=>{
return(
<div>
    props.map((item)=>
    <div key={
   item.questionId} header={
   item.questionName}>
        <p>{
   item.comments}</p>
    </div>
    )
</div>
);
};
export default QaQuestion;

从上面可以看出,容器中传递的参数已经被使用了。

Dva启动文件

默认是index.js,当然这个可以在.roadhogrc配置文件中配置。以下是index.js的内容

import dva from 'dva';
import './styles/common.css';
// 1. Initialize
const app = dva();  

// 2. Plugins 
// app.use({}); 

// 3. Model
app.model(require('./models/login'));
app.model(require('./models/qa'));

// 4. Router
app.router(require('./router'));  

// 5. Start 
app.start('#root');    

在dva中,项目启动主要分为以下几个过程:第一步,实例化一个dva对象;第二步,添加需要使用的插件;第三步,添加需要使用的模型;第四步,添加路由配置;第五步,调用dva中的start方法,该方法接收一个参数。该参数是html文件中某个元素的id,作为整个应用程序的挂载点。

hml文件默认为public目录下的index.html文件。以下是 html 文件的内容。这很简单。 body标签下只有一个div标签。该 div 充当整个应用程序的安装点。其中还有一位

<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
    <div id="root"></div>
    <script src="index.js"></script>
</body>
</html>

 
 
  
. . .

相关推荐

额外说明

jar 在服务器上运行 shell 脚本

aa-flink.jar目前 start.sh echo "start aa-flink" cd /usr/local/aa-flink source /etc/profile nohup java -jar /usr/local/aa-flink/aa

额外说明

RabbitMq学习——Springboot集成rabbitmq扇出广播配置(三)

一、简介 fanout配置是一种不用配置routingkey的一中交换机形式。 只要队列和指定的交换机进行了绑定配置,当交换机收到生产者推送来的消息后,就会将消息广播至已绑定好的队列中。 流程如图所示: 二、fanout交换机的配置 2.1、配置文件编写

额外说明

leetcode216(组合求和三:二进制枚举)

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 示例 1: 输入: k = 3, n = 7 输出: [[1,2,4]] 题解(一):这道题的枚举总数最多不过是2^9个,我们可以直接遍

额外说明

MYSQL_Explain概述,详细解释 id, select_type, table, type, possible_keys, key, key_len, ref, rows, Extra column

文章目录 ①. Explain概述 ②. 构建SQL坏境语句 ③. id字段 ④. select_type、table字段 ⑤. type字段 ⑥. possible_keys、key列字段 ⑦. key_len字段 ⑧. ref、rows字段 ⑨. E

额外说明

详解DDPG算法:解决对大量的超参数、随机重启、任务环境敏感问题,完成月球着陆器,双足机器人demo、以及超参数调优教学

0.demo展示 当我复现强化学习算法 DDPG 时,我发现论文中缺少必要的实现细节,例如:Gamma、噪声方差、最大训练步数等参数的取值。此外,在我调整参数,成功完成某次训练后,当我对随机种子进行修改,发现训练时长有很大变化,甚至有时候无法完成训练。更

额外说明

Node-new URL()使用

// 引入http const http=require("http") // 创建服务 const server=http.createServer((req,res)=>{ // 向客服端发送的信息 res.end("hell

额外说明

linux二进制安装mysql 5.7.33

二进制安装mysql centos7 二进制安装mysql 5.7.33 官方文档: https://dev.mysql.com/doc/refman/5.7/en/binary-installation.html 下载地址: https://dev.m

额外说明

redis 和 memcached 的区别,redis 持久化机制,缓存雪崩和缓存穿透问题解决方案

区别: 支持的数据存储类型不一样:redis支持丰富的数据类型。memcache支持简单的数据类型,String。 redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。而Memecache把数据全部存在内存之中。

额外说明

vulnhub打靶--lampiao

目录 vulnhub--lampiao 1.扫描主机端口,发现1898端口部署web 2.打开robots.txt发现CHANGELOG.txt文件 3.发现drupal更新日志,drupal这个版本有公开exp。利用msf打下 4.执行uname -a

额外说明

【cmd】批处理:bat 文件执行,cmd 会显示中文乱码,Jenkins 日志显示中文乱码 —— 解决方案

目录 一、批处理命令执行乱码情况 二、解决方法 一、批处理命令执行乱码情况 (1)Jenkins批处理命令乱码: (2)cmd窗口显示乱码: 二、解决方法 (1)【最关键的步骤!】bat文件编码格式修改为ANSI格式: (2)cmd窗口乱码解决: 修改注

ads via 小工具