dva react 详解_react中 安装dva不使用dva cli - CSDN
  • React+DVA开发实践

    2018-01-09 17:27:01
    本文档在前面章节简单的介绍了React和其相关的一系列技术,最后章节介绍了React+Dva开发的整套过程和基本原理,也就是将一系列框架整合的结果。 文档结构 本文档划分为以下章节,前面几个章节是知识储备,最后...

    原文链接

    文档概述

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

    文档结构

    本文档划分为以下章节,前面几个章节是知识储备,最后章节是项目实践
    
    • React
    • Flux
    • Redux
    • React-Router
    • Roadhog
    • Ant Design
    • DVA
    • 项目实践

    约束定语

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

    React

    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 也是醉了!其次呢,另一个好处就是它可以让你书写ES6之类的语法,就和CoffeeScript是一个道理,最终它会翻译到浏览器兼容的语法。

    虚拟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场景;
    • 可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护;
    • 可测试:每个组件都是独立的,所以方便测试。

    数据流props、state

    在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的变化而变化。

      哪些组件应该有state? 
      大部分组件的工作应该是从props里取数据并渲染出来,但有时需要对用户输入、服务器请求或者时间变化等作出响应,这时才需要state。组件应该尽可能的无状态化,这样能隔离state,把它放到最合理的地方(Redux做的就是这个事情?),也能减少冗余并易于解释程序运作过程。 
      常用的模式就是创建多个只负责渲染数据的无状态(stateless)组件,在他们的上层创建一个有状态(stateful)组件并把它的状态通过props传给子级。有状态的组件封装了所有的用户交互逻辑,而这些无状态组件只负责声明式地渲染数。 
      哪些应该作为state? 
      state应该包括那些可能被组件的事件处理器改变并触发用户界面更新的数据.这中数据一般很小且能被JSON序列化。当创建一个状态化的组件的时候,应该保持数据的精简,然后存入this.state.在render()中在根据state来计算需要的其他数据.因为如果在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版本中,将不在支持这种方式创建组件,因此这种方式并不推荐使用。

    extends 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创建的组件,其状态state是通过getInitialState方法来配置组件相关的状态;React.Component创建的组件,其状态state是在constructor中像初始化组件属性一样声明的。

    组件生命周期

    在组件的整个生命周期中,随着该组件的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或者state渲染之前,调用该方法。但是不可以在该方法中更新state和props。

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

    componentWillUnmount:该方法会在组件被移出之前调被调用。在componentDidMount方法中添加的所有任务都需要在该方法中撤销,比如说创建的定时器或者添加的事件监听等。

    Flux

    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

    Redux 的作用跟 Flux 是一样的,它可以看作是 Flux 的一种实现,但是又有点不同。

    • Redux 只有一个 store 。Flux 里面会有多个 store 存储应用数据,并在 store 里面执行更新逻辑,当 store变化的时候再通知 controller-view 更新自己的数据,Redux 将各个 store 整合成一个完整的 store,并且可以根据这个 store 推导出应用完整的 state。同时 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/#/login 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

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

    Ant Design

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

    DVA

    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 以下是项目目录截图: 
    这里写图片描述

    • components:最基础的组件。这里面存放的只是最基本的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里面引用的一般都是container组件,通过配置,将路径和对应的要在浏览器上加载的组件对应起来,再通过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嵌套,即在一个router里面嵌套另外一个router,这种情况下,在访问子router对应的路径时,会先加载父router对应的组件,然后再父组件里面加载子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里面需要有namesapce这个属性值

    • 外部使用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};
    },
    },
    };
    

    以上新建了一个namespace 为 ‘qa’的model,并在effects里面添加了一个fetchGuide方法和在reducers里面添加了一个guideSave方法。

    Subscriptions里面的内容表示在项目启动加载model的时候就会执行,dispatch({ type: ‘fetchGuide’,payload:{}});就相当于在项目启动的时候,就会先调用一次effects里面的fetchGuide方法;

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

    reducers里面的 方法负责改变store中的值,其实也只有通过这种方式才能改变store中的值。

    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);
    

    以上就是一个container组件,当然,上面的写法其实有点多余:const qaQuestion 这里其实生成的还是一个components组件,然后将QaQuestion 这个components组件包装到qaQuestion 组件里面,这里有点多余。但是不影响我们分析问题。Connect是redux提供的一个函数,作用是将数据和组件连接起来,也就是所谓的向components组件传递数据。在这里我们传递了一个qa参数,其实这是一个namespace名为qa的model,当然,数据最终是存储在store下面。也就是说,我们通过connect这个函数,可以直接拿到store里面的数据(model也是在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;
    

    从上可以看出,已经使用到了container里面传过来的参数。

    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对象;第二步是添加需要使用到的插件;第三步是添加需要使用到的model;第四部是添加路由配置;第五步是调用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>
    
    展开全文
  • ReactDva的学习

    2019-09-11 09:09:29
    一、创建Dva工程 我们前两篇都是用create-react-app创建工程的,但是它只能创建一个最基本的空程序。在前两篇中,我们自己用npm装上了router,redux等依赖包,并自己手动写了很多操作。 Dva将上述一切进行了简化,它...

    一、创建Dva工程

    我们前两篇都是用create-react-app创建工程的,但是它只能创建一个最基本的空程序。在前两篇中,我们自己用npm装上了router,redux等依赖包,并自己手动写了很多操作。
    Dva将上述一切进行了简化,它是一个封装好很多模块的框架,并且拥有自己的脚手架。用Dva创建的工程,从目录结构起就非常清晰。(虽然框架这种东西可以简化很多操作,但在使用框架前还是不能忘记本源,所以建议前两篇也粗略的看一看。)

    先安装dva-cli脚手架

    npm install dva-cli -g
    

    dva -v可以查看版本号
    在这里插入图片描述
    跟create-react-app一样,用dva在命令行创建app。

    dva new dva-app   //这里的dva new 是创建项目的命令   dva-app是我们新建的项目名
    

    其后进入工程文件并启动工程。

    cd dva-app
    npm start
    

    在这里插入图片描述
    Dva工程的目录结构如下:
    在这里插入图片描述
    可以看出,比create-react是多了很多东西的。
    除了index.js的入口文件,和router.js的初始路由,上面那些文件夹里大致意义如下:

    • asserts 用于存放静态资源,跟以往正常的java工程一样,里面扔图片视频之类的。
    • components 用于存放公共组件,比如页面头尾导航条。
    • routes 用于存放路由组件,可以通俗的理解成页面。与component的区别在于一般是非公共的并且会跟model里的数据模型联系起来。
    • models 用于存放模型文件,(个人认为model才是react-redux体系里最重要的部分),我们之前说的state以及reducer都会写在里面,同时,通过connect跟routes里面的组件进行绑定。routes中的组件通过connect拿到model中的数据,model也能通过connect接收到组件中dispatch的action。
    • services 用于存放跟后台交互的服务文件,一般都需要调用后台的接口。
    • utils 用于存放工具类库。

    其他的配置文件暂时不管啦。

    整体的流程大致(数据流向)是:
    从index入口进入 => 通过url匹配routes里的组件(顺便看看components里有没有啥公共组件要加载过来) => routes里的组件dispatch action => model通过action改变state(中途可能会通过services跟后台交互) => 通过connect重新渲染routes中的组件
    在这里插入图片描述

    二、实践出真知

    理论不如实践,那么,我们用Dva搞一个前两篇中的例子试一试,路由切换并且做个表格。(其实具体各个函数的写法跟前两篇中基本一样,只是写的位置不一样。下面详解。)
    在这里插入图片描述

    在这里插入图片描述
    首页

    入口文件:

    首先,在入口文件index.js中,能看到已经给我们留了model模型和router路由的位置。在开发中,所有的model模型文件都需要在这里注册,这样才能用connect跟组件联系起来。

    // 1. Initialize
    const app = dva();
    
    // 2. Plugins
    // app.use({});
    
    // 3. Model   //先暂时不管Model
    // app.model(require('./models/example').default);
    
    // 4. Router
    app.router(require('./router').default);
    
    // 5. Start
    app.start('#root');
    
    

    先不管Model,Router告诉我们引用了router.js,打开router.js文件看一眼如下。 下面有代码讲解

    import IndexPage from './routes/IndexPage';
    ……
        <Router history={history}>
          <Switch>
            <Route path="/" exact component={IndexPage} />
          </Switch>
        </Router>
    

    讲解:通俗易懂,在路径为/的时候匹配到IndexPage。(说明:不用我们自己装Router,我们建立router.js以后,框架自动给我们配置了Router,是不是很方便~)注意这里加了exact,表示精确匹配,如果不加则/aaa, /bbb, /whatever都会匹配到IndexPage,精确匹配的话只有输入/的时候才会匹配。
    IndexPage里能看到欢迎信息,那个小丑图片和提示文字,这里就不贴过来了。

    路由切换:

    下面我们自己做个路由切换的效果。

    Step1: 装Antd!

    虽然Dva很贴心的封装了很多工具包但是并没有封装样式库,所以Antd还是需要自己装的。

    npm install antd
    npm install babel-plugin-import
    

    并编辑.webpackrc文件。如下所示

    {
    "extraBabelPlugins": [
        ["import", { 
        "libraryName": "antd",
        "libraryDirectory": "es",
        "style": true
        }]
      ]
    }
    

    这样就可以使用Antd了。

    Step2: 新增组件

    下一步我们需要新建一些组件:
    在这里插入图片描述
    我们在routes里面新建aaa.jsbbb.js两个组件。内容随便写点啥,反正只是为了切换用。
    ① aaa.js

    // aaa.js
    import React, { Component } from 'react'
    import { connect } from 'dva'
    
    class AAA extends Component { //BBB同理
        render() {
            return (
                <div>
                   <h1>AAA</h1>
                </div>
            )
        }
    }
    
    AAA.propsTypes = {}
    export default connect()(AAA)  //connect函数将store对象与本组件联系在一起
    

    Step3: 定义导航栏

    然后,在components里建立两个公共组件header.jslayout.js
    header.js用作页面上方的导航栏,理想效果如下,通过点击不同导航切换地址栏url。
    在这里插入图片描述
    header.js需要引入antd的Menu模块,以及dva封装好的LinkLink的作用即匹配相应的路径,在爬坑之路二里我们也用到过。

    // header.js
    ……
    import { Menu } from 'antd';
    import { Link } from 'dva/router'
    
    class Header extends Component {
        render() {
            return (
                <Menu
                  theme="dark"
                  mode="horizontal"
                  defaultSelectedKeys={['1']}
                  style={{ lineHeight: '64px' }}
                >
                  <Menu.Item key="1">
                        <Link to="/">Index</Link>
                  </Menu.Item>
                  <Menu.Item key="2">
                        <Link to="/aaa">AAA</Link>
                  </Menu.Item>
                  <Menu.Item key="3">
                        <Link to="/bbb">BBB</Link>
                  </Menu.Item>
                </Menu>
            )
        }
    }
    ……
    

    Step4: 定义布局

    layout.js用来当整体页面布局,我们想把页面分成两部分,上面放导航条下面放子内容。头部需要引入刚刚的header组件,然后以<Header />的方式插入到节点中。props是layout.js组件的属性(解释:就是其他组件向Layout组件传值的时候,Layout组件用props来接收),用于传递数据,每个组件都有自己的props。props.children表示该组件的所有子节点。

    // layout.js
    import React, { Component } from 'react'
    import { connect } from 'dva';
    import Header from '../components/header'
    
    class Layout extends Component {
        render() {
            const { children } = this.props
            return (
                <div>
                    <Header />
                    <div style={{ background: '#fff', padding: 24 }}>{children}</div>
                </div>
            )
        }
    }
    
    export default Layout;
    

    Step5: 连接路由

    此时还差最后一步,就是把路由跟刚刚的这些组件联系起来。我们回到router.js,修改如下。

    // router.js
    import React from 'react';
    import { Router, Route, Switch } from 'dva/router';
    import Layout from './components/layout'
    import IndexPage from './routes/IndexPage';
    import AAA from './routes/aaa';
    import BBB from './routes/bbb';
    
    function RouterConfig({ history }) {  //这里的history是存储历史浏览记录的栈
      return (
        <Router history={history}>  //{history}固定写法
            <Layout>   //下面有代码讲解
                  <Switch>
                    <Route path="/" exact component={IndexPage} />
                    <Route path="/aaa" exact component={AAA} />
                    <Route path="/bbb" exact component={BBB} />
                  </Switch>
            </Layout>
        </Router>
      );
    }
    
    export default RouterConfig;
    

    讲解:
    <Layout></Layout>标签括起来的那部分就是父组件传给 Layout组件的值。然后在Layout组件内部,通过props属性做接收,从而展示在Layout组件中

    以上操作的流程是,在header.js中,我们通过点击不同导航栏,更换地址栏的url;接下来在router.js中,通过匹配地址栏的url,选择加载的子组件呈现到页面上。
    最终的效果应如下所示:
    在这里插入图片描述
    在这里插入图片描述
    路由到此结束,接下来加上列表。

    列表操作:

    Step1:创建routes页面组件

    routes/IndexPage.js页面组件里,我们把爬坑之路二的那个列表拿过来。

    在这里插入图片描述

    //IndexPage.js部分代码如下
      <div>
            <Button type="primary" onClick={this.changeData}>修改数据</Button>
            <div>
                <Table columns={columns} dataSource={data}/>
            </div>
        </div>
    

    Table标签有两个属性,columns是表头,dataSource是数据,均绑定到state。只是这一次,state和action都不是直接写在页面组件下的了,而是定义到对应的模型文件中。

    Step2:创建models模型

    我们新建models/indexpage.js作为模型文件。可以参考项目自带的example.js,里面已经给出了state,effects和reducers的地方。

    //models/indexpage.js   //下面有代码讲解
    export default {    
      namespace: 'indexpage',
      state: {
        columns: [{
              title: '姓名',
              dataIndex: 'name',
            }, {
              title: '性别',
              dataIndex: 'gender',
            }],
        data: [{
            "key": "1",
            "name": "王大斌",
            "gender": "男"
          },{
            "key": "2",
            "name": "刘小洋",
            "gender": "男"
          }]
      },
    
      subscriptions: {
      ……
      },
    
      effects: {
      ……
      },
    
      reducers: {
      ……
      },
    };
    

    讲解namespace是该模型文件的命名空间,也是在全局state上的属性,在routes组件中,我们可以用this.props.某命名空间获取state(根节点)中某命名空间的数据。

    Step3:通过connect关联数据

    回到routes/IndexPage.js页面组件,如果我们在routes中想要用model里面的数据的话,我们需要通过connect工具来传递,其基本用法是connect(从model中获取数据)(绑定到routes组件)

    //routes/IndexPage.js
    import { connect } from 'dva';
    
    class IndexPage extends Component{
        changeData = () => {
            console.log("Change Data");
        };
    
        render() {     //因为在下面的export default connect已经把model层里的indexpage中的state绑     定到本组件了
            const { columns, data } = this.props.indexpage; //获取indexpage中的state
            console.log(this.props);
            return (
                <div>
                    <Button type="primary" onClick={this.changeData}>修改数据</Button>
                    <div>
                        <Table columns={columns} dataSource={data}/>
                    </div>
                </div>
            )
        }
    }
    //这样models/indexpage.js的state数据就被绑定到routes/IndexPage.js的table节点上啦,列表被赋予了两行值。
    export default connect(({ indexpage }) => ({  /connect()里面的函数体为什么这样写????
      indexpage,
    }))(IndexPage);
    

    Step4:创建services获取数据

    假设我们有个json数据是这样的,放在8080端口,我们想把它当做一行添加到上面的列表里。
    在这里插入图片描述
    在services中我们同后台进行交互,新建services/user.js文件。

    //services/user.js
    import request from '../utils/request';
    
    export function getUser() {     //request方法用于向后台发送请求  
      return request('http://localhost:8080/data');
    }
    

    utils/request.js里面已经贴心的自带了request方法用于请求url,直接拿来用就可以。
    这样,在我们调用getUser()方法时,就会得到请求数据啦。
    友情提示,以往我们一直习惯直接在model里调用getUser()然后用setstate改变state的值,但这是不符合Dva的规则的,Dva规定我们必须通过触发action才可以改变state,也就是下一步。

    Step5:触发action

    对于这个列表,我们希望点击按钮添加一行。根据前面的讲述我们知道了,改变state的值需要dispatch(action),再调用reducer改变state,进而重新渲染,这是Dva规定的数据流传输方式。
    这里的action有两种走向,对于同步行为(比如我就改变个按钮颜色),可以直接调用model中的reducer。而对于异步行为(比如从服务器请求数据),需要先触发effects然后调用reducer。
    下面我们在routes/IndexPage.js里,通过onClick={this.changeData}按钮事件,触发一个action给模型文件。

    //点击按钮触发
        changeData = () => {
            const { dispatch } = this.props;
            dispatch({   //通过dispatch向models层发送一个请求
              type: 'indexpage/addUser',  //这表示我们要调用models/indexpage.js的addUser方法。
              payload: {},
            });
        };
    

    这表示我们要调用models/indexpage.jsaddUser方法。
    models/indexpage.js里,我们需要在effects中定义好这个方法。
    effects 属性的语法可以参考Redux-saga 中文文档,它可以获取全局state,也可以触发action。effects内部使用 yield 关键字标识每一步的操作,call表示调用异步函数,put表示dispatch(action)。

    effects: {    //因为该函数里面有yield处理异步函数,所以函数名前面有*,这里的*是ES6里generator function的一种语法,     函数的第一个参数是payload,这是默认约定俗成的参数名,我们应该也可以改成其他的参数名,参数名叫什么都可以,无所谓的。函数的第二个参数对象是 effect 原语集,其中 call, put 最为常用
        *addUser({ payload }, { call, put }) {  // eslint-disable-line
          const myuser = yield call(userService.getUser, {});
          yield put({  
            type: 'ADD_USER',
            payload: {
                  myuser:myuser.data
            },
          });
        },
      },
    

    这表明,我们先读取service中的getUser()方法,得到返回结果,然后将它放到payload里,然后调用了model层里面的reducer里面名为ADD_USER的action,告诉它要改变state。

    Step5:调用reducer

    我们说过,reducer是纯函数,当输入相同时输出也必然相同,其输入参数是当前的state和收到的action,他会返回一个新的 state。
    ...三点运算符在爬坑二中说过了哦,作用是把数组打开进行操作。我们把payload中的信息加在state的原有data之后。

     reducers: {
        ADD_USER(state, action) {
          return { ...state, 
            data:state.data.concat(action.payload.myuser) }; //把payload中的信息加在state的原有data之后
        },
      },
    

    点击一下按钮,是不是已经添加成功了~ 还记得在爬坑之路二中,我们需要手动去创建监听重新渲染页面才能看到更新效果,而在Dva中,由于使用了connect,会在内部自动更新,并不用手动刷新了呢。

    最终的成果如下所示:
    在这里插入图片描述
    这样,就完成了一个简单的页面切换和修改state的例子。

    本文转载自:https://www.jianshu.com/p/513c5eab17c1

    展开全文
  • react是会有很多疑惑, 不知道需要用到ES6的哪些知识点; react component 有三种写法,都要掌握还是 .... JavaScript语言 变量声明 const 和 let 不要用var, 而是用const和let, 分别表示常量和变量。不同于...

    学react是会有很多疑惑,

    • 不知道需要用到ES6的哪些知识点;
    • react component 有三种写法,都要掌握还是
    • ....

    JavaScript语言

    变量声明

    const 和 let
    不要用var, 而是用const和let, 分别表示常量和变量。不同于var的函数作用域, const和let都是块级作用域

    const DELAY = 1000;
    
    let  count = 0;
    count = count + 1;
    

    模板字符串
    模板字符串提供了另一种做字符串组合的方法。

    const user = 'world';
    console.log(`hello ${user}`);    //hello world
    
    //多行
    const content = `
        Hello ${firstName},
        Thanks for ordering ${qty} tickets to ${event}.
    `;

    默认参数

    function  logActivity(activity = 'skiing') {
        console.log(activity);
    }
    
    logAcitity();    //skiing

    箭头函数
    函数的快捷写法,不需要通过function关键字创建函数,并且还可以省略return关键字。同时,箭头函数还会继承当前四昂下文的this关键字。
    比如:

    [1, 2, 3].map(x => x + 1)

    等同于:

    [1, 2, 3].map((function(x) {
        return x + 1;
    }).bind(this));

    模块的import和export
    import用于引入模块,export用于导出模块
    比如:

    //引入全部
    import dva from 'dva';
    
    //引入部分
    import { connect } from 'dva';
    import { Link, Route } from 'dva/router';
    
    //引入全部并作为github对象
    import * as github from './service/github';
    
    //导出默认
    export default App;
    //部分导出, 需import { App } from './file'; 引入
    export class App extend Component {};
    

    ES6对象和数组
    析构赋值

    析构赋值让我们从Object或Array里取出部分数据存为变量

    //对象
    const user = { name: 'guanguan', age: 2};
    const { name, age } = user;
    console.log(`${name} : ${age} `);        //guanguan : 2
    
    //数组
    const arr = [1, 2];
    const [foo, bar] = arr;
    console.log(foo);

    我们也可以析构传入的函数参数

    const add = (state, { payload } => {
        return state.concat(payload);
    });

    析构时还可以配alias(别名),让代码更具有语义

    const add = (state, { payload: todo }) => {
        return state.concat(todo);
    };

    对象字面量改进

    这是析构的反向操作,用于重新组织一个object

    const name = 'duoduo';
    const age = 8;
    
    const user = { name, age };    //{ name: 'duoduo', age: 8};

    定义对象方法时,还可以省去function关键字

    app.model({
        reducers: {
            add() {} //等同于 add: function() {}
        },
        effects: {
            *addRemote() {}    //等同于addRemote: function*() {}
        },
    });    

    Spread Operator

    Spread Operator即3个点...,有几种不同的使用方法
    可用于组装数组。

    const name = 'duoduo';
    const age = 8;
    
    const user = { name, age };    //{ name: 'duoduo', age: 8};

     也可用户获取数组的部分项

    const arr = ['a', 'b', 'c'];
    const [first, ...rest] = arr;
    rest;    //['b', 'c']
    
    //with ignore
    const [first, , ...rest] = arr;
    rest;    //['c']

     代替apply

    function foo(x, y, z){}
    const args = [1, 2, 3];
    
    //下面两句效果相同
    foo.apply(null, args);
    foo(...args);

    对于Object而言,用于组合成新的Object

    const foo = {
        a: 1,
        b: 2,
    };
    
    const bar = {
        b: 3,
        c: 2
    };
    
    const d = 4;
    
    const ret = {...foo, ...bar,d };    //{ a: 1, b: 3, c: 2, d: 4}

     promise

    promise用于更优雅地处理异步请求。比如发起异步请求:

    fetch('/api/todos')
        .then(res => res.json())
        .then(data => ({ data }))
        .catch(err => ({ err }));
     

    定义Promise

    const delay =(timeout) => {
        return new Promise(resolve => {
            setTimeout(resolve, timeout);
        });
    };
    
    delay(1000).then(_ => {
        console.log('executed');
    });

    Generators
    dva的effects是通过generator组织的。generator返回的是迭代器,通过yield关键字实现暂停功能
    这是一个典型的dva effect,通过yield把异步逻辑通过同步的方式组织起来

    app.model({
        namespace: 'todos',
        effects: {
            *addRemote({ payload: todo }, { put, call }) {
                yield call(addTodo, todo);
                yield put({ type: 'add', payload: todo });
            },    
        },
    });

    React Component

    Stateless Functional Component

    react Compnent 有三种定义方式,分别是React.createClass, class和Stateless Functional Component。推荐使用最后一种,保持简洁和无状态,这是函数,不是Object,没有this作用域。

    比入定义App Component

    function App(props){
        function handleClick(){
            props.dispatch({ type: 'app/create' })
        }
        return <div onClick={handleClick}>${props.name}</div>
    }

    JSX

    Component嵌套

    类似HTML,JSX里可以给组件添加子组件

    <App>
        <Header />
        <MainContent />
        <Footer />
    </App>

    className

    class是保留字,所以添加样式时,需用className替代class

    <h1 className = "fancy">hello dva</h1>

    JavaScript表达式

    javascript表达式需要用{}括起来,会执行并返回结果。

    <h1>{this.props.title}</h1>

    Mapping Arrays to JSX

    可以把数组映射为JSX元素列表

    <ul>
        { this.props.todos.map((todo, i) => <li key={i}>{todo}</li>)}
    </ul>

    注释

    尽量别用“//”做单行注释

    <h1>
        {/* multiline comment */}
        {/*
            multi
            line
            comment
        */}
        {
            //single line
        }
        Hello
    </h1>

    Spread Atrribute

    这是JSX从ECMAScript6借鉴过来的很有用的特性,用于扩充组件props

    别如:

    const attrs = {
        href: 'http://example.org',
        target: '_blank',    
    };
    <a {...attrs}>Hello</a>

    等同于

    const attrs = {
        href: 'http://example.org',
        target: '_blank',    
    }
    <a href = {attrs.href} target = {target.target}>Hello</a>

    Props

    数据处理在React中是非常重要的概念之一,分别可以通过props,state和context来处理数据。而在dva中你只需要关心props

    proptypes

    JavaScript是弱类型语言,所以请尽量声明propTypes对props进行校验,以减少不必要的问题

    function App(props) {
        return <div>{props.name}</div>;
    }
    App.propTypes = {
        name: React.PropTypes.string.isRequired,
    };

    内置的proptype有:

    • PropTypes.array
    • PropTypes.bool
    • PropTypes.func
    • PropTypes.number
    • PropTypes.object
    • PropTypes.string

    往下传数据:

    往上传数据:

    CSS Modules

    理解css Modules

    一张图理解CSS Modules的工作原理

    buttom class在构建之后会被重命名为ProductList_button_1FU0u.button是本地名称。而 produc'tList_button_1FU0Ou是global name。你可以用简短的描述性名字,而不需要关心命名冲突问题。

    然后你要做的全部事情就是在css/less文件里写.button{...},并在组件里通过style.button来引用他。

    定义全局CSS

    CSS Modules默认是局部作用域的,想要声明一个全局规则,可用:global语法

    .title {
        color: red;
    }
    
    :global(.title) {
        color: green;
    }

    然后在引用的时候:

    <App className={styles.title} />         //red
    <App className="title" /}                //green

    className Package

    在一些复杂的场景中,一个元素可能对应多个classname,而每个className又基于一些条件来决定是否出现。这时,classname这个库就非常有用。

    import classnames from 'classnames';
    const App = (props) => {
        const cls = classnames({
            btn: true,
            btnLarge: props.type === 'submit',
            btnSmall: props.type === 'edit',
        });
        return <div className = { cls }/>
    };

    这样,传入不同的type给App组件,就会返回不同的className组合:

    <App type = "submit" />         //btn btnLarge
    <App type = "edit" /}           //btn btnSmall

    Reducer

    reducer是一个函数,接受state和action,返回老的或新的state。即:(state, action)=> state.

    增删改

    以todos为例。

    app.model({
        namespace: 'todos',
        state: [],
        reducer: {
            add(state, { payload: todo}){
                return state.concat(todo);
            },
            remove(state, { payload: id }) {
                return state.filter(todo => todo.id != id)
            },
            update(state, { payload: updateTodo }) {
                return state.map(todo => {
                    if(todo.id === updatedTodo.id) {
                        return {...todo, ...updatedTodo};
                    } else {
                        return todo;
                    }
                });
            },
        },
    });

    深层嵌套的例子,应尽量避免。

    Effect

    app.model({
        namespace: 'todos',
        effects: {
            *addRemote({ payload: todo },{ put, call }){
                yield call(addTodo, todo);
                yield put({ type: 'add', payload: todo });
            },
        },
    });

    effects

    put

    用于触发action。

    yield put({ type: 'todo/add', payload: 'Learn Dva' });

    call

    用于调用异步逻辑,支持promise

    const result = yield call(fetch, '/todos');

    select

    用于从state里获取数据

    const todos = yield select(state => state.todos);

    错误处理

    全局错误处理

    dva里,effects和subscriptions的抛错全部会走onError hook,所以用在onError里统一处理错误。

    const app = dva({
        onError(e, dispatch) {
            console.log(e.message);
        },
    });

    然后effects里的抛错和reject的promise就都会被捕获到了。

    本地错误处理

    如果需要对某些effects的错误进行特殊处理,需要在effect内部加 try , catch

    app.model({
        effect: {
            *addRemote() {
                try{
                    //your code here
                } catch(e) {
                    console.log(e.message);
                }
            },
        },
    });

    异步请求

    异步请求基于whatwg-fetch

    GET和POST

    import request from '../util/request';
    
    //GET
    request('/api/todos');
    
    //POST
    request('/api/todos', {
        method: 'POST',
        body: JSON.stringify({a: 1}),
    });

    统一错误处理

    假如约定后台返回一下格式时, 做统一的错误处理

    {
        status: 'error',
        message: '',
    }

    编辑utils/request.js,加入以下中间件。

    function parseErrorMessage({ data }) {
        const { status, message } = data;
        if(status === 'error' ){
            throw new Error(message);
        }
        return { data };
    }

    然后,这类错误就会走到onError hook里。

    Subscription

    subscription是订阅,用于订阅一个数据源,然后根据需要dispatch相应的action。数据源是可以是当前的时间/服务器的websocket连接,keyboard输入,geolocation变化,history路由变化等。格式为({ dispatch, history })=> unsubscribe

    异步数据初始化

    比如:当用户进入/users页面时,触发action user/fetch加载用户数据。

    app.model({
        subscriptions: {
            setup({ dispatch, history })=>{
                history.listen(({ pathname }) => {
                    if(pathname === '/users') {
                       dispatch({
                            type: 'users/fetch',
                        });
                    }
                });
            },
        },
    });

    path-to-regexp Package

    如果url规则比较复杂,比如/users/:userId/search,那么匹配和userId的获取都会比较麻烦这里推荐用path-to-regexp简化这部分逻辑

    import pathToRegexp from 'path-to-regexp';
    
    // in subscription
    const match = pathToRegexp('/users/:userId/search').exec(pathname);
    if (match) {
      const userId = match[1];
      // dispatch action with userId
    }

    Router

    config with JSX Element (router.js)

    <Route path = "/" component = {App} />
        <Route path = "accounts" component = {Accounts} />
        <Route path = "statements" compnent = {Statements} />
    </Route>

    Router Components

    Route Components 是指 ./src/routes/目录下的文件,他们是./src/router.js里匹配

    通过connect绑定数据

    import { connect } from 'dva';
    function App() {}
    
    function mapStateToProps(state, ownProps) {
        return {
            users: state.users,
        };
    }
    export default connect(mapStateToProps)(App);
    

    然后在App里就有了dispatch和users两个属性

    injected Props(e.g.location)

    Route Compotent 会有额外的props用以获得路由信息

    • location
    • params

    基于action进行页面跳转

    import { routerRedux } from 'dva/router';
    
    //Inside Effects
    yield put(routerRedux.push('/logout'));
    
    //Out Effects
    dispatch(routerRedux.push('/logout'));
    
    //with query
    routerRedux.push({
        pathname: '/logout',
        query: {
            page: 2,
        },
    });

    除push(location) 外还有更多方法

    dva配置

    Redux Middleware

    比如要添加redux-logger中间件:

    import createLogger from 'redux-logger';
    
    const app = dva({
        onAction: createLogger(),
    });

    注:onAction支持数组,可同时传入多个中间件

    history

    切换history为browserHistory

    import { browserHistory } from 'dva/router';
    const app = dva({
        history: browserHistory,
    });

    去除hashHistory下的_k查询参数

    import { useRouterHistory } from 'dva/router';
    import { createHashHistory } from 'history';
    const app = dva({
        history: userRouterHistory(createHashHistory)({querykey: false}),
    });

    工具

    通过dva-cli创建项目

    先安装dva-cli

    $ npm install dva-cli -g

    然后创建项目

    $ dva new myapp

    最后,进入目录并启动

    $ cd myapp
    $ npm start

     

     

     

     

     

    展开全文
  • 我们首先搭建一个 dva Demo 项目(请参考react快速构建一个应用项目),然后逐步完成以下内容: 结构划分 设计 Model 组件设计方法 组件设计实践 添加 Reducers 添加 Effects 定义 Service mock 数据 ...

    请点击相应的步骤查看详情

    我们首先搭建一个 dva Demo  项目(请参考react快速构建一个应用项目),然后逐步完成以下内容:

    1. 结构划分
    2. 设计 Model
    3. 组件设计方法
    4. 组件设计实践
    5. 添加 Reducers
    6. 添加 Effects
    7. 定义 Service
    8. mock 数据
    9. 添加样式
    10. 设计布局

    第一步,我们会划分一下整体的项目结构,熟悉每一部分是什么概念;接下来我们会说如何抽离 model,以及 model 设计的一些思路;然后我们会根据项目的情况说明如何合理的设计你的组件,以及组件中样式的处理;在设计好了组件之后,就会进入数据相关的内容,包含了同步和异步的情况,以及异步请求的处理方式,在最后我们还会介绍在dva中mock数据的的方式以及布局的设计。

     在 dva 架构的项目中,我们推荐的目录基本结构为:

    .
    ├── /mock/           # 数据mock的接口文件
    ├── /src/            # 项目源码目录
    │ ├── /components/   # 项目组件
    │ ├── /routes/       # 路由组件(页面维度)
    │ ├── /models/       # 数据模型
    │ ├── /services/     # 数据接口
    │ ├── /utils/        # 工具函数
    │ ├── route.js       # 路由配置
    │ ├── index.js       # 入口文件
    │ ├── index.less     
    │ └── index.html     
    ├── package.json     # 项目信息
    └── proxy.config.js  # 数据mock配置

     

    最终我们得到的项目是这样的:

    user-dashboard


    详情请参考:https://github.com/dvajs/dva-docs

    转载于:https://www.cnblogs.com/wheatCatcher/p/8489447.html

    展开全文
  • 欢迎大家关注【跨平台开发那些事】公众号,定期推...基于最新版本React Native实现JsBundle预加载,界面秒开优化 一、开源库介绍 今年1月份,新开源的react-natvigation库备受瞩目。在短短不到3个月的时间,gith...
  • 基于create-react-app官方脚手架搭建dva模式的项目;以react官方脚手架开启项目,安装采用dva方式的,构建项目,展示记录过程中遇到的各种点和Keng,若你也遇到类似问题,也许能帮上你 基于上一版本增加完善部分
  • 1. config文件单独打包 在前端项目根目录建立文件webpack.config.js ,若存在,则在里面修改代码。 webpack.config.js里面代码如下。 let webpack = require('webpack'); let path = require('path');...
  • 一.项目创建 可以参考官方文档 1.安装 dva-cli 通过 npm 安装 dva-cli 并确保版本是0.9.1或以上。(“$符”代表你的文件路径,如:D:\Dvajs_workspace\) ...$ dva new dva-quickstart dva-quicks..
  • 2、使用的是dva的脚手架新建的项目 目录结构 3、首先拿到一个接口,需要在/services/api.js进行传入, import request from '../utils/request'; import networkUtils from '../utils/networkUtils'; export asy...
  • dva框架的使用详解及Demo教程 在前段时间,我们也学习讲解过Redux框架的基本使用,但是有很多同学在交流群里给我的反馈信息说,redux框架理解上有难度,看了之后还是一脸懵逼不知道如何下手,很多同学就转向选择...
  • dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。 学过React的童鞋都知道它的技术栈真的很多,所以...
  • 在整个游戏的各个模块中,Redux ,mobx,以及蚂蚁金服的 ant-design,dva, umi 这些框架或者第三方库都有涉及使用。但是自从了解了Facebook官方提案的 Hooks 特性后,才真正觉得获得了前所未有的解脱。如果你有React...
  • dva项目结构及运用

    2019-07-08 22:09:43
    dva是一个整合react-redux、react-saga的一个数据流方案,同时将react-router也装入进来了,dva简化了redux和saga的文件目录,将这两个数据工具统一放置在一个目录(models)之内,一个对象里面包含了同步异步操作。...
  • 19年全新React教程全家桶实战redux+antd+dva+Hooks前端js视频 ...
  • 项目配置到这里之后,接下来就需要处理以下配置了:国际化store的数据存储结构 国际化和store数据结构的设计:此处采用immutable数据格式(immutable一款很棒的数据操作工具,此处不做详解,有兴趣的同学可自行学习...
  • React开发调试工具

    2017-05-11 10:42:35
    首先,下载react-eveloper-tools开发调试工具插件。 官网下载路径:https://github.com/facebook/react-devtools 谷歌下载.crx后缀的文件,火狐下载.xpi后缀的文件。 因为谷歌插件下载需要翻墙,这里提供一个本地...
  • dva框架的使用详解及Demo教程 Dva 是什么 初识 Dva 关于dva框架的简单操作以及demo 结合官方文档: 此处不再赘述dva项目初始化的5个api(const myapp=dva()/plugins(dva-loading)/model(require)/router/start...
  • dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。 易学易用,仅有 6 个 api,对 redux 用户尤其友好,...
1 2 3 4 5 ... 9
收藏数 175
精华内容 70
关键字:

dva react 详解