组件化

需求一到,接就是怎么实现,技术选型自然成为了第一个问题。鉴于目前web前端mvvm框架以及组件化开发方式的流行,决定技术栈采用:vue + es6 + 组件化。

这里首先简单说下web前端组件化开发方式的历程:

最早的组件化结构,代码结构可能如下:

- lib/components/calendar|- calendar.css|- calendar.js|- calendar.html

 

将同功能的组件文件放到同一目录下,结构清晰、职责明确,视图、样式、脚本的关系显著,也易于单元测试,是独立展示和交互的最小单元。

后来:

在之前基础上对组件进行了生命周期的加工(初始化、获取资源、渲染、更新、销毁等),理顺了组件的各个阶段,有助于对组件实现(从初始化到销毁)的理解。并且借助于组件各个阶段的钩子可以对组件有更好的利用和扩展。对外暴露接口,数据绑定或者说数据仓库的加入,各种xMD模块加载器的出现,也让这种这种开发方式上升了一个层级。ExtJs、YUI等都是这方面的专家。

再后来:

有了之前发展,进步是很大的,但依然不够。组件的可复用性(基础样式,基础逻辑,基础属性、可复用的稳定业务逻辑等)、组件间通信、全局状态管理、甚至是能否有更好的代码组织方式等依然是问题。Angular、React、Polymer、Vue等mvvm框架和webpack、browserify等构建、预编译工具的出现正试图解决这些问题。

 

ES6

在正式开始vue之前,因为本项目用到了es6,那么就谈谈大家都关注的EcmaScript6。多余的就不说了,es6经历了多年的苦,终于在2015年下半年定稿,正式名称:EcmaScript2015。每个刚开始接触es6的人应该都有这么一个问题,es6的出现到底是为了什么,或者说它解决了什么。老版本es4/5虽然坑多,就像Brendan Eich评价js一样:"优秀之处并非原创,原创之处并不优秀"。但我们不也是去其槽粕,留其精髓,一路填坑走过了吗?

来直接一点,es6常用的特性有:class类的支持、箭头函数、对象和数组的解构、默认参数、不定参数、对象合并、let与const关键字、for of迭代、字符串模板、对象字面量增强、同名对象字面量缩写、模块化import/export、map、promise、* yeild生成器等。

这里挑出几个常用的简单说下:

首先class:

在没有class的时候,创建类的一种比较标准的方式是将非函数的属性放到构造函数里,函数属性在原型链里添加。类的继承的实现就更为多样:对象冒充、call/apply方式、原型链方式等。es6的class和extends关键字的出现给出了一个统一的规范

复制代码
class People {constructor (name, age, gender){this.name = name}sayName (){return this.name}
}class Student extends People {constructor (name, age, gender, skill){super(name, age, gender)this.skill = skill}saySkill (){return this.skill}
}let tom = new Student('tom', 16, 'male', 'computer')tom.sayName()  // => 'tom'
tom.saySkill()  // => 'computer'

tom.__proto__ == Student.prototype // => true
Student.__proto__ == People // => true
复制代码

 

可以看出虽然是新的规范,但是还是遵守js的原则:对象的__proto__指向它的构造函数(类)的prototype。es6对象字面量的__proto__注入也能快速的实现继承。

接下来是let:

es6之前js只有函数作用域,let的出现有了块级作用域,也就算是if、else、for这类也有了作用域,块内用let声明的变量外面是访问不到的,在js预解析的时候,是不会被提升到当前函数作用域的前面的。基于该特性,在for迭代的时候,每次迭代都会产生一个块级作用域的独立的迭代变量,让最后的结果就是我们期待的结果。

复制代码
var arr = [];
for (let i = 0; i < 10; i ++){arr[i] = function (){return i}
}arr[6]()  // => 6//如果用var声明i,无论多少次迭代,外层的i始终被每次迭代的函数内部引用着(闭包),不会被当做垃圾回收,最后的结果都指向同一个i,值为10。
//以往为了避免这个问题,通常会这么做:for (var i = 0; i < 10; i ++){arr[i] = (function (i){return function (){return i}})(i)
}
复制代码

 

最后讲讲箭头函数:

es6之前的function有一个特点:函数内部的上下文并不是由该函数写在那里决定的,而是由谁调用决定的,谁调用函数内部的this就指向谁。然后我们有些时候并不想让他这样,但又没办法,只能通过先保存this,或者call/apply,或者bind来调整上下文。箭头函数的出现解决了这个宁人苦恼的问题,因为箭头函数内的上线文(this)是由函数写在哪决定的,无论被哪个对象调用,上下文都不会改变。

复制代码
var obj = {test1 : function (){window.setTimeout(function (){console.info(this)}, 100)},test2 : function (){window.setTimeout(() => {console.info(this)}, 100)}
}obj.test1() // => Window
obj.test2() // => obj
复制代码

 

用普通函数还是箭头函数并非绝对,箭头函数也不能完全替代普通函数,要用哪个由具体逻辑决定,前提是要先了解他们的区别。

箭头函数还有一个特点就是能够简化return的书写。

复制代码
var a = function (n){return n
}var b = (n) => n //可以省略return和花括号var c = n => n //如果只有一个参数,中括号也可以省略

a(1)  // => 1
b(1)  // => 1
c(1)  // => 1
复制代码

 

 从这几个简单的例子可以看出,es6不仅仅是新增了几颗糖,对之前js的一些不友好的地方的改善才是重点。

 

Vue

进入正题,

Vue.js(读音 /vjuː/, 类似于 view)是一个构建数据驱动的 web 界面的库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

Vue.js 自身不是一个全能框架——它只聚焦于视图层。因此它非常容易学习,非常容易与其它库或已有项目整合。另一方面,在与相关工具和支持库一起使用时,Vue.js 也能完美地驱动复杂的单页应用。

 

— 文中关于vue的大部分内容引用自vue的官方文档,感谢作者的工作!

 

响应的数据绑定:

<div id="app"><p>{{ message }}</p><input v-model="message">
</div>
复制代码
new Vue({el : '#app',data : {message : 'Hello Vue.js!'}
})
复制代码

 

结果:改变输入框的值,<p>标签的文本也会对应改变。

 

基本工作原理:<input>输入框的值与vue实例的message属性进行了绑定,<p>标签的文本也与message属性进行了绑定。输入框值的变化会改变message的值,message值的变化会反应到<p>标签的文本上。

 

Vue.js 的核心是一个响应的数据绑定系统,它让数据与 DOM 保持同步非常简单。在使用 jQuery 手工操作 DOM 时,我们的代码常常是命令式的、重复的与易错的。Vue.js 拥抱数据驱动的视图概念。通俗地讲,它意味着我们在普通 HTML 模板中使用特殊的语法将 DOM “绑定”到底层数据。一旦创建了绑定,DOM 将与数据保持同步。每当修改了数据,DOM 便相应地更新。这样我们应用中的逻辑就几乎都是直接修改数据了,不必与 DOM 更新搅在一起。这让我们的代码更容易撰写、理解与维护。

组件系统:

组件系统是 Vue.js 另一个重要概念,因为它提供了一种抽象,让我们可以用独立可复用的小组件来构建大型应用。如果我们考虑到这点,几乎任意类型的应用的界面都可以抽象为一个组件树:

 

实际上,一个典型的用 Vue.js 构建的大型应用将形成一个组件树。

你可能已经注意到 Vue.js 组件非常类似于自定义元素——它是 Web 组件规范的一部分。实际上 Vue.js 的组件语法参考了该规范。例如 Vue 组件实现了 Slot API 与 is 特性。但是,有几个关键的不同:

Web 组件规范仍然远未完成,并且没有浏览器实现。相比之下,Vue.js 组件不需要任何补丁,并且在所有支持的浏览器(IE9 及更高版本)之下表现一致。必要时,Vue.js 组件也可以放在原生自定义元素之内。

Vue.js 组件提供了原生自定义元素所不具备的一些重要功能,比如组件间的数据流,自定义事件系统,以及动态的、带特效的组件替换。

组件系统是用 Vue.js 构建大型应用的基础。另外,Vue.js 生态系统也提供了高级工具与多种支持库,它们和 Vue.js 一起构成了一个更加“框架”性的系统。

 

这里简单介绍下vue最常用也较重要的两块:响应式原理和组件系统。

 

响应式原理:

 

Vue.js的数据观测实现原理和Angular有着本质的不同。了解Angular的读者可能知道,Angular的数据观测采用的是脏检查(dirty checking)机制。每一个指令都会有一个对应的用来观测数据的对象,叫做watcher;一个作用域中会有很多个watcher。每当界面需要更新时,Angular会遍历当前作用域里的所有watcher,对它们一一求值,然后和之前保存的旧值进行比较。如果求值的结果变化了,就触发对应的更新,这个过程叫做digest cycle。

脏检查有两个问题:

1.任何数据变动都意味着当前作用域的每一个watcher需要被重新求值,因此当watcher的数量庞大时,应用的性能就不可避免地受到影响,并且很难优化。
2.当数据变动时,框架并不能主动侦测到变化的发生,需要手动触发digest cycle才能触发相应的DOM 更新。Angular通过在DOM事件处理函数中自动触发digest cycle部分规避了这个问题,但还是有很多情况需要用户手动进行触发。


Vue.js采用的则是基于依赖收集的观测机制。从原理上来说,和老牌MVVM框架Knockout是一样的。依赖收集的基本原理是:

1.将原生的数据改造成 “可观察对象”。一个可观察对象可以被取值,也可以被赋值。
2.在watcher的求值过程中,每一个被取值的可观察对象都会将当前的watcher注册为自己的一个订阅者,并成为当前watcher的一个依赖。
3.当一个被依赖的可观察对象被赋值时,它会通知所有订阅自己的watcher重新求值,并触发相应的更新。
4.依赖收集的优点在于可以精确、主动地追踪数据的变化,不存在上述提到的脏检查的两个问题。但传统的依赖收集实现,比如Knockout,通常需要包裹原生数据来制造可观察对象,在取值和赋值时需要采用函数调用的形式,在进行数据操作时写法繁琐,不够直观;同时,对复杂嵌套结构的对象支持也不理想。


Vue.js利用了ES5的Object.defineProperty方法,直接将原生数据对象的属性改造为getter和setter(这是ES5的特性,需要js解释引擎的支持,无法通过各种打shim补丁来实现。这也是为什么Vue不支持IE8及以下版本的原因),在这两个函数内部实现依赖的收集和触发,而且完美支持嵌套的对象结构。对于数组,则通过包裹数组的可变方法(比如push)来监听数组的变化。这使得操作Vue.js的数据和操作原生对象几乎没有差别[注:在添加/删除属性,或是修改数组特定位置元素时,需要调用特定的函数,如obj.$add(key, value)才能触发更新。这是受ES5的语言特性所限。在操作对象类型数据的时候一定要注意这点,否则无法实现响应。

 

变化检测:

受 ES5 的限制,Vue.js 不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应的。例如:

复制代码
var data = {a : 1}
var vm = new Vue({data : data
})
//vm.a 和 data.a 现在是响应的

vm.b = 2
//vm.b 不是响应的

data.b = 2
//data.b 不是响应的
复制代码

 

不过,有办法在实例创建之后添加属性并且让它是响应的。

对于 Vue 实例,可以使用 $set(key, value) 实例方法:

vm.$set('b', 2)
//vm.b 和 data.b 现在是响应的

对于普通数据对象,可以使用全局方法 Vue.set(object, key, value):

Vue.set(data, 'c', 3)
//vm.c 和 data.c 现在是响应的

有时你想向已有对象上添加一些属性,例如使用 Object.assign() 或 _.extend() 添加属性。但是,添加到对象上的新属性不会触发更新。这时可以创建一个新的对象,包含原对象的属性和新的属性:

// 不使用 Object.assign(this.someObject, {a : 1,b : 2})
this.someObject = Object.assign({}, this.someObject, {a : 1, b : 2})

 

计算属性的奥秘:

你应该注意到 Vue.js 的计算属性不是简单的 getter。计算属性持续追踪它的响应依赖。在计算一个计算属性时,Vue.js 更新它的依赖列表并缓存结果,只有当其中一个依赖发生了变化,缓存的结果才无效。因此,只要依赖不发生变化,访问计算属性会直接返回缓存的结果,而不是调用 getter。

为什么要缓存呢?假设我们有一个高耗计算属性 A,它要遍历一个巨型数组并做大量的计算。然后,可能有其它的计算属性依赖 A。如果没有缓存,我们将调用 A 的 getter 许多次,超过必要次数。

由于计算属性被缓存了,在访问它时 getter 不总是被调用。考虑下例:

复制代码
var vm = new Vue({data : {msg : 'hi'},computed : {example : function (){return Date.now() + this.msg}}
})
复制代码

 

计算属性 example 只有一个依赖: vm.msg 。 Date.now()  不是 响应依赖,因为它跟 Vue 的数据观察系统无关。因而,在访问  vm.example  时将发现时间戳不变,除非  vm.msg  变了。

有时希望 getter 不改变原有的行为,每次访问  vm.example  时都调用 getter。这时可以为指定的计算属性关闭缓存:

复制代码
computed : {example : {cache : false,get : function (){return Date.now() + this.msg}}
}
复制代码

现在每次访问  vm.example  时,时间戳都是新的。但是,只是在 JavaScript 中访问是这样的;数据绑定仍是依赖驱动的。如果在模块中这样绑定计算属性  {{example}} ,只有响应依赖发生变化时才更新DOM。

组件系统

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。

1.创建和注册组件:

可以用  Vue.extend()  创建一个组件构造器:

var MyComponent = Vue.extend({template : '<div>A custom component!</div>'
})

 

要把这个构造器用作组件,需要用  Vue.component(tag, constructor)  注册(这个注册是全局的):

//全局注册组件,tag 为 my-component
Vue.component('my-component',  MyComponent)

 

组件在注册之后,便可以在父实例的模块中以自定义元素  <my-component>  的形式使用。要确保在初始化根实例之前注册了组件:

<div id="example"><my-component></my-component>
</div>

 

最后渲染为:

<div id="example"><div>A custom component!</div>
</div>

当然,可以让组件只能用在其它组件内,用实例选项 components 注册,比如:

复制代码
var Child = Vue.extend({ /* ... */ })var Parent = Vue.extend({template : '...',components : {// <my-component> 只能用在父组件模板内'my-component': Child}
})
复制代码

 

这种局部注册的方式也适用于其它资源,比如指令、过滤器和过渡。他们都支持全局和局部组件注册。

前面提到组件是可以被复用的,多个实例可能会共享一个组件构造器,那么请注意一个组件选项的问题:

传入 Vue 构造器的多数选项也可以用在  Vue.extend() 中,不过有两个特例: data 和 el。试想如果我们简单地把一个对象作为 data 选项传给  Vue.extend() :

var data = {a : 1}
var MyComponent = Vue.extend({data : data
})

这么做的问题是 MyComponent 所有的实例将共享同一个 data 对象!因为对象是引用传递的,这基本不是我们想要的,因此我们应当使用一个函数作为 data 选项,让这个函数返回一个新对象:

var MyComponent = Vue.extend({data : function (){return {a : 1}}
})

同理,el 选项用在  Vue.extend()   中时也须是一个函数。

 

 2.使用props传递数据

当一个组件内部还有一个子组件的时候,由于组件实例的作用域是孤立的,这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。这时,父组件可以使用props把数据传给子组件:

“prop” 是组件数据的一个字段,期望从父组件传下来。子组件需要显式地用 props 选项 声明 props:

Vue.component('child', {//camelCase in JavaScriptprops : ['myMessage'],template : '<span>{{ myMessage }}</span>'
})

然后向它传入一个普通字符串:

<child my-message="hello!"></child>

子组件的渲染结果:

由于命名的习惯,请注意camelCase和kebab-case:HTML 特性不区分大小写。名字形式为 camelCase 的 prop 用作特性时,需要转为 kebab-case(短横线隔开)。

根据vue响应的特性,props也可以是动态的:

类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用  v-bind  绑定动态 Props 到父组件的数据。每当父组件的数据变化时,也会传导给子组件:

<div><input v-model="parentMsg"><br/><child v-bind:my-message="parentMsg"></child>
</div>

也可以使用v-bind的缩写语法来简化绑定:

<child :my-message="parentMsg"></child>

渲染结果:

改变输入框的值,子组件的文本会跟着改变

关于props的其他介绍,请参考 :props

 

3.父子组件的通信

子组件可以用  this.$parent  访问它的父组件。根实例的后代可以用 this.$root 访问它。父组件有一个数组  this.$children ,包含它所有的子元素。

尽管可以访问父链上任意的实例,不过子组件应当避免直接依赖父组件的数据,尽量显式地使用 props 传递数据。另外,在子组件中修改父组件的状态是非常糟糕的做法,因为:

这让父组件与子组件紧密地耦合;

只看父组件,很难理解父组件的状态。因为它可能被任意子组件修改!理想情况下,只有组件自己能修改它的状态。

Vue 实例实现了一个自定义事件接口,用于在组件树中通信。这个事件系统独立于原生 DOM 事件,用法也不同。

每个 Vue 实例都是一个事件触发器:

  • 使用  $on()  监听事件;

  • 使用  $emit()  在它上面触发事件;

  • 使用  $dispatch()  派发事件,事件沿着父链冒泡;

  • 使用  $broadcast()  广播事件,事件向下传导给所有的后代。

不同于 DOM 事件,Vue 事件在冒泡过程中第一次触发回调之后自动停止冒泡,除非回调明确返回  true 。

一个简单的例子:

复制代码
<!-- 子组件模板 -->
<template id="child-template"><input v-model="msg"><button v-on:click="notify">Dispatch Event</button>
</template><!-- 父组件模板 -->
<div id="events-example"><p>Messages: {{ messages | json }}</p><child></child>
</div>
复制代码

 

在子组件的输入框输入值以后,点击按钮,父组件的Messages:[]文本会对应变化

 

4.再来说说动态组件

多个组件可以使用同一个挂载点,然后动态地在它们之间切换。使用保留的  <component> 元素,动态地绑定到它的 is 特性:

复制代码
new Vue({el : 'body',data : {currentView : 'home'},components : {home : { /* ... */ },posts : { /* ... */ },archive : { /* ... */ }}
})
复制代码
<component :is="currentView"><!-- 组件在 vm.currentview 变化时改变 -->
</component>

如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个  keep-alive  指令参数:

<component :is="currentView" keep-alive><!-- 非活动组件将被缓存 -->
</component>

其他动态组件的详细介绍,请参考:动态组件

在创建复杂应用的时候,动态组件或许就显得不那么灵活了,这时可以使用路由,vue-router路由扩展可以看做是动态组件的升级版,可参考:vue-router

 

5.最后,组件实例的生命周期:

Vue 实例在创建时有一系列初始化步骤——例如,它需要建立数据观察,编译模板,创建必要的数据绑定。在此过程中,它也将调用一些生命周期钩子,给自定义逻辑提供运行机会。例如 created 钩子在实例创建后调用:

复制代码
var vm = new Vue({data : {a : 1},created : function (){// this 指向 vm 实例console.log('a is: ' + this.a)}
})
// => "a is: 1"
复制代码

也有一些其它的钩子,在实例生命周期的不同阶段调用,如 compiled、 ready 、destroyed。钩子的 this 指向调用它的 Vue 实例。一些用户可能会问 Vue.js 是否有“控制器”的概念?答案是,没有。组件的自定义逻辑可以分割在这些钩子中。

声明周期的图示:

组件的简单介绍就到这里。

 

Vuex

在大型应用中,状态管理常常变得复杂,因为状态分散在许多组件内,在不同的作用域内。以vue来说,当使用vue-router以及组件化开发(.vue)来构建大型单页应用的时候,组件之间状态的数据的传递会很困难,虽然props、dispatch、broadcast等能够进行跨组件的数据传递,但是大量使用它们会使组件之间的耦合程度很高,组件越多,层级越多,维护起来就越复杂。怎么办呢?能否在全局提供一个状态管理构架?

这里得提出一个概念:Flux

Flux是Facebook用来构建用户端的web应用的应用程序体系架构。它通过利用数据的单向流动为React的可复用的视图组件提供了补充。相比于形式化的框架它更像是一个架构思想,不需要太多新的代码你就可以马上使用Flux构建你的应用。

Flux应用主要包括三部分:dispatcher、store和views(React components),千万不要和MVC(model-View-Controller)搞混。Controller在Flux应用中也确实存在,但是是以controller-view的形式。view通常处于应用的顶层,它从stores中获取数据,同时将这些数据传递给它的后代节点。另外,action creators - dispatcher辅助方法 - 一个被用来提供描述应用所有可能存在的改变的语义化的API。把它理解为Flux更新闭环的第四个组成部分可以帮助你更好的理解它。

一句话:Flux就是手动将Action从数据流底层视图中的事件手动绑定到数据顶层的数据流架构。

单向数据流的设计目的:任何UI不能直接对数据有写操作,就是防止同一份数据有多个地方同时在写。相对于直接进行双向绑定,编码稍微会复杂一点,但换来了排错和维护的便捷。

 

Flux 架构常用于 React 应用中,但它的核心理念也可以适用于 Vue.js 应用。比如 Vuex 就是一个借鉴于 Flux,但是专门为 Vue.js 所设计的状态管理方案。React 生态圈中最流行的 Flux 实现 Redux 也可以通过简单的绑定和 Vue 一起使用。

 

什么是Vuex

Vuex 是一个专门为 Vue.js 应用所设计的集中式状态管理架构。它借鉴了 Flux 和 Redux 的设计思想,但简化了概念,并且采用了一种为能更好发挥 Vue.js 数据响应机制而专门设计的实现。

为什么需要它?

当你的应用还很简单的时候,你多半并不需要 Vuex。也不建议过早地使用 Vuex。但如果你正在构建一个中型以上规模的 SPA,你很有可能已经需要思考应该如何更好地归纳 Vue 之外,应用的其他组成部分。这就是 Vuex 要大显身手的时刻。

我们在单独使用 Vue.js 的时候,通常会把状态储存在组件的内部。也就是说,每一个组件都拥有当前应用状态的一部分,整个应用的状态是分散在各个角落的。然而我们经常会需要把状态的一部分共享给多个组件。一个常见的解决策略为:使用定制的事件系统,让一个组件把一些状态“发送”到其他组件中。这种模式的问题在于,大型组件树中的事件流会很快变得非常繁杂,并且调试时很难去找出究竟哪错了。

为了更好的解决在大型应用中状态的共用问题,我们需要对组件的 组件本地状态(component local state) 和 应用层级状态(application level state) 进行区分。应用级的状态不属于任何特定的组件,但每一个组件仍然可以监视(Observe)其变化从而响应式地更新 DOM。通过汇总应用的状态管理于一处,我们就不必到处传递事件。因为任何牵扯到一个以上组件的逻辑,都应该写在这里。此外,这样做也能让我们更容易地记录并观察状态的变更(Mutation,原意为突变),甚至可以实现出华丽如时光旅行一般的调试效果。(译注:是时候安利一波 vue-devtools 了)

Vuex 也对如何管理分撒各地的状态增加了一些约束,但仍保留有足够面对真实使用场景的灵活性。

一定需要它吗?

Vuex有这么多好处,但这并不代表我们一定就要在项目中使用它。假如我们的项目是一个管理平台系统,一般无非是列表跳转详情这种搭配,不同列表页面、不同详情页面之间没有什么相互关联或者需要共享的状态,也不会出现某一个需要获取到所有详情页面的状态这种需求。这时候我们是不需要Vuex的,使用它只会增加项目的复杂度。下面将介绍到的活动发布系统,最后创建的时候需要获取到所有组件的数据,这个时候使用Vuex显得十分有必要。如果你都不知道是否需要Vuex,那就不用它。

最简单的store

创建 Vuex store 的过程相当直截了当 - 只要提供一个初始化的 state 对象,以及一些 mutations:

复制代码
import Vuex from 'vuex'const state = {count : 0
}const mutations = {INCREMENT (state){state.count ++}
}export default new Vuex.Store({state,mutations
})
复制代码

现在,你可以通过  store.state   来读取 state 对象,还可以通过 dispatch 某 mutation 的名字来触发这些状态变更:

store.dispatch('INCREMENT')console.log(store.state.count) // -> 1

如果你倾向于对象风格的分发方式,你可以用这种语法:

// 效果同上
store.dispatch({type : 'INCREMENT'
})

再次强调,我们通过分发 mutation 的方式,而非直接改变  store.state ,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。

Vuex 使用 单一状态树 —— 是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个『唯一数据源(SSOT)』而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

以上只是一个用来展示 store 究竟是什么的一个极简例子。再谈谈三哥核心概念:State(状态),Mutations(变更) 和 Actions(动作)。

State和Getters

1.安装 Vuex 并且将您的根组件引入 store 实例:

复制代码
import Vue from 'vue'
import Vuex from 'vuex'
import store from './store'
import MyComponent from './MyComponent'// 关键点,教 Vue 组件如何处理与 Vuex 相关的选项
Vue.use(Vuex)var app = new Vue({el : '#app',store, // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
    components : {MyComponent}
})
复制代码

 

通过在根实例中注册 store 选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过  this.$store  访问到。不过事实上,我们几乎不会需要直接引用它。

2.在子组件中,通过在  vuex.getters  选项里定义的 getter 方法来读取状态:

复制代码
// MyComponent.js
export default {template : '...',data (){ ... },// 此处为我们从 store 实例中取回状态的位置
    vuex : {getters : {// 该 getter 函数将会把仓库的 `store.state.count` 绑定为组件的 `this.count`count : (state) => state.count}}
}
复制代码

 

请留意 vuex 的这个特殊选项(译注:getters 子对象)。它是我们指定当前组件能从 store 里获取哪些状态信息的地方。它的每个属性名将对应一个 getter 函数。该函数仅接收 store 的整个状态树作为其唯一参数,之后既可以返回状态树的一部分,也可以返回从状态树中求取的计算值。而返回结果,则会依据这个 getter 的属性名添加到组件上,用法与组件自身的计算属性一毛一样。

组件不能直接修改store实例的状态:

请始终记得非常重要的这点,就是:组件永远都不应该直接改变 Vuex store 的状态。因为我们想要让状态的每次改变都很明确且可追踪,Vuex 状态的所有改变都必须在 store 的 mutation handler (变更句柄) 中管理。为了强化该规则,在开启(严格模式(Strict Mode))时,若有 store 的状态在 mutation 句柄外被修改,Vuex 就会报错。现在有了这一规则,我们 Vue 组件的职能就少了很多:他们通过只读的 getter 与 Vuex store 的状态相绑定,组件唯一能影响全局状态的方法就是想办法触发 mutations(我们接下来会谈到)。若有必要,组件仍然能够处理和操作本地状态,但是我们不再在单独的组件中放置任何数据请求或全局状态变更的逻辑。这些操作全部都集中于 Vuex 相关的文件中,这样能让大型应用变得更容易理解和维护。

 

Mutation

Mutations 本质上是一个事件系统:每个 mutation 都有一个 事件名 (name) 和 一个 回调函数 (handler). 任何一个 Mutation handler 的第一个参数永远为所属 store 的整个 state 对象:

复制代码
import Vuex from 'vuex'const store = new Vuex.Store({state : {count : 1},mutations : {INCREMENT (state){// 改变 statestate.count ++}}
})
复制代码

用全部大写命名 mutation 是一个惯例,方便将它和 actions 区分开。

你不能直接调用 mutation handler. 这里传入 Store 构造函数的选项更像是在注册事件回调:当INCREMENT 事件被触发时,调用这个 handler。触发 mutation handler 的方法是 dispatch 一个 mutation 的事件名:

store.dispatch('INCREMENT')

 

Mutation必须是同步函数:

  因为当 mutation 触发的时候,回掉函数还没有被调用,我们不知道什么时候回调函数实际上被调用。任何在回调函数中进行的的状态的改变都是不可追踪的。

Mutation必须遵守Vue的响应系统规则:

  1.尽可能在创建 store 时就初始化 state 所需要的所有属性。

  2.当添加一个原本不存在的属性时,需要使用  Vue.set(obj, 'newProp', 123)  或者拷贝并替换原本的对象。利用 stage 2 的语言特性 object spread syntax,我们可以使用这样的语法:   state.obj = {...state.obj, newProp : 123} 

Actions

Actions 是用于分发 mutations 的函数。按照惯例,Vuex actions 的第一个参数是 store 实例,附加上可选的自定义参数。

复制代码
// 最简单的 action
function increment (store){store.dispatch('INCREMENT')
}// 带附加参数的 action
// 使用 ES2015 参数解构
function incrementBy ({dispatch}, amount){dispatch('INCREMENT', amount)
}
复制代码

乍一眼看上去感觉多此一举,我们直接分发 mutations 岂不更方便?实际上并非如此,还记得 mutations 必须同步执行这个限制么?Actions 就不受约束!我们可以在 action 内部执行异步操作,比如执行一个ajax请求数据的操作:

复制代码
function getData ({dispatch}){ajax ({url : "...",data : {...},success : (data) => {dispatch("SET_DATA", data)}})
}
复制代码

我们可以这样在组件中调用actions:

复制代码
// 某组件内部// 导入actions
import {incrementBy} from './actions'const vm = new Vue({vuex : {getters : { ... }, // state getters
        actions : {incrementBy}}
})
复制代码

上述代码所做的就是把原生的 incrementBy action 绑定到组件的 store 实例中,暴露给组件一个  vm.increamentBy  实例方法。所有传递给  vm.increamentBy 的参数变量都会排列在 store 变量后面然后一起传递给原生的 action 函数,所以调用  vm.incrementBy(1) 等价于  incrementBy(vm.$store, 1) 。虽然多写了一些代码,但是组件的模板中调用 action 更加省力了:

<button v-on:click="incrementBy(1)">increment by one</button>

通常在大型 App 中,action 应该按不同目的进行 分组 / 模块化 的管理,具体请参考: Actions

 

下面再谈谈一个重要的东西,数据流

为了更好地理解 Vuex app 中的数据流,我们来开发一个简单的计数器 app。注意:这个例子仅仅是为了更好地解释概念,在实际情况中并不需要在这种简单的场合使用 Vuex.

复制代码
// store.js
import Vue from 'vue'
import Vuex from 'vuex'Vue.use(Vuex)// 应用初始状态
const state = {count : 0
}// 定义所需的 mutations
const mutations = {INCREMENT (state){state.count ++},DECREMENT (state){state.count --}
}// 创建 store 实例
export default new Vuex.Store({state,mutations
})
复制代码
// actions.js
export const increment = ({ dispatch }) => dispatch('INCREMENT')
export const decrement = ({ dispatch }) => dispatch('DECREMENT')
复制代码
<!- temmplate -->
<div>Clicked: {{ count }} times<button v-on:click="increment">+</button><button v-on:click="decrement">-</button>
</div>
复制代码
复制代码
// 仅需要在根组件中注入 store 实例一次即可
import store from './store'
import {increment, decrement} from './actions'const app = new Vue({el : '#app',store,vuex : {getters: {count: state => state.count},actions: {increment,decrement}}
})
复制代码

你会注意到组件本身非常简单:它所做的仅仅是绑定到 state、然后在用户输入时调用 actions。

你也会发现整个应用的数据流是单向的,正如 Flux 最初所定义的那样:

1.用户在组件中的输入操作触发 action 调用。
2.Actions 通过分发 mutations 来修改 store 实例的状态。
3.Store 实例的状态变化反过来又通过 getters 被组件获知。

最后:Vuex 并不强制要求所有的状态都必须放在 Vuex store 中 ,如果有些状态你觉得并没有需要对其变化进行追踪,那么你完全可以把它放在 Vuex 外面(比如作为组件的本地状态)。比如公共组件对外的接口,通过props传递数据更为有效。

Vuex的完整介绍请参考:Vuex

 

vue-devtools

vue-devtools是chrome的一个vue开发插件,可以在chrome商店下载crx扩展包进行安装。提供Components和Vuex预览(state变化跟踪等)功能,有助于开发和调试。

 

可以看到组件的prop属性、计算属性、vue getter属性等,以及Vuex中的触发的mutation、state 当前的值等我们可能关注的内容都直观地展示了出来。

 

Vue模块化 

 

对于大型项目,为了更好地管理代码使用模块构建系统非常必要。推荐代码使用 CommonJS 或 ES6 模块,然后使用 Webpack 或 Browserify 打包。

Webpack 和 Browserify 不只是模块打包器。两者都提供了源码转换 API,通过它可以用其它预处理器转换源码。例如,借助 babel-loader 或 babelify 代码可以使用 ES2015/2016 语法。

你可以使用 Webpack + vue-loader 或 Browserify + vueify 构建这些单文件 Vue 组件。

选择哪种构建工具取决于你的经验和需求。Webpack 的功能更强大,如代码分割,将静态资源当作模块,提取组件的 CSS 到单独的一个文件等,不过它的配置相对复杂一点。如果你不需要 Webpack 的那些功能,使用 Browserify 更简单,最快的构建方式是使用官方出品的脚手架工具 vue-cli。参考:vue-cli

 

 

活动模板设计系统

这个设计系统只是对活动模板要展示的内容进行设计,具体的样式和交互由活动h5页面根据视觉和交互设计来定夺。活动里面的每一个子项都可以抽象为一个组件,h5展示端拿到每个组件的内容再套上对应组件的样式和交互逻辑,最终就形成了一个h5活动页面。

每一个活动组件对应三个模式组件:

  1.标签组件,通过拖动来创建对应类型的组件

      2.预览组件,展示当前组件各项的内容

      3.编辑组件,用来编辑当前选中的组件的各项内容

完成后大概是这样的,以一个最简单的节标题组件为例:

 

如上图所示:左侧容器排列着这些常用组件的标签。将活动需要的组件标签拖入预览区域后,会生成对应的预览组件和编辑组件;点击这个预览组件,组件编辑区域会显示对应的编辑组件;在编辑组件中可以对组件各项进行编辑。编辑完成后,通过事先的数据绑定,预览区域对应的组件就会更新视图,显示组件当前的最新内容。

以上就是这个系统的一个大概方案,下面谈谈具体的实现。

 

首先,从标签区域开始:

标签组件是每个活动组件的开端,也就说每一个标签组件必须有一个属性来标识它代表的是哪一个活动组件。那就先给它们指定类型 type:

  节标题  type :'sectionTitle' 

  投票  type :'vote'

  正文  type :'content' 

  用户  type :'user' 

  图片  type :'image' 

  视频  type :'video' 

  音频  type :'audio' 

  跳转链接  type :'link'

然后每当我们拖动一个标签组件到预览区域,再根据该标签组件的type生成对应的预览和编辑组件。预览和编辑组件需要确定的无非就是有哪些编辑项,这些编辑项是什么内容。以节标题组件为例,它就只有一个编辑项:节标题的文本。也就是说节标题的预览组件用来显示节标题的文本,编辑组件需要有一个文本域来对节标题文本进行编辑,在模板事先绑定好对应的数据,文本域的数据变化会反应到预览组件的DOM上。

我们需要有一个保存所有组件数据(对象)的容器,可以使用一个数组。

我更喜欢操作一个数组而不是对象的原因:vue对数组的基本方法(push、splice、shift等)都进行了封装,通过这些方法来改变数组的数据,结果都是响应的。而在保持响应的情况下,改变对象的数据要麻烦些,特别是复杂的嵌套对象。如果使用对象可以通过id直接匹配到对应数据,通过数组需要遍历一下。但是有了es6的for of,代码还是很简单,而且也不是在操作DOM,性能影响不大。

复制代码
//widgetData.js

[{id : "100",type : "vote", ...}, //投票{id : "101",type : "image", ...}, //图片{id : "102",type : "video", ...}, //视频
]
复制代码

每个组件数据对象的id属性是唯一的,是拖入标签组件时生成的,这个id属性是关联预览组件与对应编辑组件的关键,通过它可以找到每个预览组件对应的编辑组件。为什么不通过type来判断呢?因为每个活动可能有多个相同的组件,比如节标题。通过type没法确定对应关系。

这里我们通过Vuex创建一个store来存储及修改这个数组(官方点的说法就是管理state状态)。按照上面提到的Vuex的数据流规则:UI不允许直接修改数据。在编辑项里面改变某项输入框的值,并不是直接改变了对应组件数据中那一项的值,而是通过DOM事件触发对应的action,action再派发对应的mutaion处理函数来修改state。这种方式可以确保所有对某项组件数据的修改都是通过触发某一个公共的action来完成的,这个action就是进行某项修改的统一和唯一的入口。

 

当我们知道需要生成什么预览和编辑组件的时候,并放进组件数据容器的时候,我们就必须知道这个组件到底有哪些编辑项(除了组件类型外,我们放入的这个组件数据对象还需要哪些属性),这时候我们就需要一个map,来管理组件type和组件编辑项的关系,以活动的投票组件为例:

根据需求,投票组件需要有以下编辑项:

1.投票的标题

2.投票项,每项要有一个名称,后续每项可能还会有其他属性(类似正确选项的标记等)

复制代码
//typeDataMap.js

export default {vote : {type : "vote",title : "投票标题文本",items : [{name : "投票项1"},  //每个投票项{name : "投票项2"},{name : "投票项3"}]}
}
复制代码

只要知道是什么类型,通过  typeData[type]  就能获取到组件数据并存入组件数据容器了。由于我们在预览组件和编辑组件的模板视图已事先对DOM进行了数据绑定,当我们改变组件容器中某个组件的数据项时,更新就会反应到DOM上。当我们保存整个模板的时候,只需要取出组件数据容器中的值就行了,其实也就是那个数组本身。H5展示端通过这个组件数据数组,可以拿到组件的数据以及排序,按照定好的模板渲染出来即可。当然,像投票组件这类有交互数据的组件,该系统设计的模板只是确定了要展示的固定的内容。具体的投票总数、每项投票数等属性需要后端处理后插入到对应组件数据里面,供展示端显示。

整个系统大概的设计思想就是这样的,下面挑些具体的来讲讲:

 

标签组件

因为标签组件的表现和交互逻辑等都是一致的,这里做了一个公共可复用的标签组件,对外接收两个参数:title(标签文本)和type(标签类型)。在标签容器组件创建一个包含所有标签组件数据对象的数组,在模板视图中遍历这个数组,就创建了所有的标签组件。

公共标签组件的统一的属性和方法等存入了一个对象字面量里面,导入以后通过mixin方式混合,组件就会拥有这些属性和方法。目前这样做的意义不大,因为已经有一个公共的标签组件了,mixin里面的东西完全可以直接写到这个公共组件内。但如果每个类型的标签组件都是一个单独的.vue组件文件,mixin的好处就体现出来了:可复用、易维护。

具体实现的代码,省略掉样式

复制代码
//labelWrapper.vue 标签组件容器(组件标签区域)<template><div class="label-wrapper"><div class="label-title">组件标签区域</div><div class="label-box"><common-label v-for="label in labelArr" :title="label.title" :type="label.type"></common-label></div></div>
</template><script>import commonLabel from './widget/commonLabel.vue' //导入公共标签组件
export default {name : "label_wrapper",components : {commonLabel //注册为子组件(es6同名对象字面量缩写)
        },data (){return {labelArr : [{title : "节标题", type : "sectionTitle"},{title : "投票", type : "vote"},{title : "正文", type : "content"},{title : "用户", type : "user"},{title : "图片", type : "image"},{title : "视频", type : "video"},{title : "音频", type : "audio"},{title : "跳转链接", type : "link"}]}}}
</script><style lang="stylus">/*...*/
</style>
复制代码

 

复制代码
//commonLabel.vue 公共标签组件<template><div class="label-item-wrapper" title="拖入模板设计区域" draggable="true" @dragstart="dragStart"><img class="label-icon" alt="{{title}}" :src="iconUrl"><span class="label-text">{{title}}</span></div>
</template><script>//导入mixin
    import labelMixin from './mixin/labelMixin'export default {name : "label",props : {title : String,type : String},mixins : [labelMixin],computed : {iconUrl (){return  this.type + '.png'}}}
</script><style lang="stylus">/*...*/
</style>
复制代码
复制代码
//labelMixin.js

import typeDataMap from './typeDataMap'export default {methods : {dragStart (e){var id = parseInt(Date.now() + "" + parseInt(Math.random() * 90))var widgetData = typeDataMap[this.type]var dt = e.dataTransferwidgetData['id'] = iddt.setData("id", id)dt.setData("type", this.type)dt.setData("widgetData", JSON.stringify(widgetData))}}
}
复制代码

 

预览组件

预览组件相对较简单,除了数据的绑定,就是拖动排序。拖动排序的实现是通过html5原生的drag事件,基于vue数据驱动的原理,拖动的时候并不需要去手动改变预览区域内各组件的DOM顺序,只需要改变组件数据数组里面各数据对象的index即可,数据的变化会反应到DOM上。简单的节标题预览组件:

复制代码
<template><div class="preview-item-wrapper" draggable="true" :class="{'active': isActive}" @click="showEdit" @dragover="allowDrop" @dragstart="dragStart" @drop="dropIn"><span class="preview-item-del" :class="{'active': isActive}" title="删除该组件"><div v-on:click="delMe">x</div></span><label class="preview-item-label">- 节标题 -</label><div class="preview-item-input-wrapper"><div class="title-text">{{text}}</div></div></div>
</template><script>//导入action
    import {addPreviewAndData, deleteWidgetPreview, changeWidgetEdit, changPreviewAndDataIndex} from '../../../store/actions'//导入mixin
    import previewMixin from './mixin/previewMixin'export default {name : "sectionTitle_preview",mixins : [previewMixin],props : {id : Number,index : Number},computed : {//mixin外的私有属性
            text (){for (let value of this.widgetDataArr)if (value.id == this.id) return value.text}},vuex : {//绑定mixin需要的属性和方法
            getters : {widgetDataArr : (state) => state.widgetDataArr,currentEditWidgetId : (state) => state.currentEditWidgetId},actions : {addPreviewAndData,deleteWidgetPreview,changeWidgetEdit,changPreviewAndDataIndex}}}
</script><style lang="stylus">/*...*/
</style>
复制代码
复制代码
/*** previewMixin.js* 预览组件的mixin* @提取同类组件之间可复用的计算属性与方法*/export default {computed : {//该预览组件是否为当前点击的
        isActive (){return this.id == this.currentEditWidgetId}},methods : {//删除该预览组件
        delMe (){this.deleteWidgetPreview(this.id)},//显示该预览组件对应的编辑组件showEdit (){this.changeWidgetEdit(this.id)},//允许向该预览组件拖放其他组件
        allowDrop (e){e.preventDefault();},//开始拖放该预览组件
        dragStart (e){var dt = e.dataTransferdt.setData("index", this.index)},//向该预览组件拖放其他组件(预览组件或者标签组件)
        dropIn (e){e.preventDefault()e.stopPropagation()var dt = e.dataTransfervar id = parseInt(dt.getData("id"))if (id){ //有id表明拖入的是标签组件var type = dt.getData("type")var widgetData = JSON.parse(dt.getData("widgetData"))this.changeWidgetEdit(id)this.addValidation(id) //添加组件验证项} else {var index = parseInt(dt.getData("index"))this.changPreviewAndDataIndex(index, this.index)}//清空dataTransfer
            dt.clearData()}}
}
复制代码

 

编辑组件

还是以节标题组件为例:

复制代码
<template><div class="edit-item-wrapper"><label class="edit-item-label">节标题文本</label><validator name="titleValidator"><div class="edit-item-input-wrapper"><textarea class="title-edit-input" placeholder="必填项,16字以内" v-model="text" v-validate:text="{required: {rule: true,message: '请填写节标题文本'},maxlength: {rule: 16,message: '节标题文本限制在16字以内'}}" @input="inputValue" @valid="onValid" @invalid="onInvalid"></textarea><div class="edit-input-err" v-if="$titleValidator.text.required">{{$titleValidator.text.required}}</div><div class="edit-input-err" v-if="$titleValidator.text.maxlength">{{$titleValidator.text.maxlength}}</div></div></validator></div>
</template><script>//导入action
    import {changeWidgetData, changeValidation} from '../../../store/actions'//导入mixin
    import editMixin from './mixin/editMixin'export default {name : "title_edit",mixins : [editMixin],props : {id : Number},computed : {//mixin外的私有属性
            text (){for (let value of this.widgetDataArr)if (value.id == this.id) return value.text}},methods : {//mixin外的私有方法
            inputValue (e){this.changeWidgetData(this.id, 'text', e.target.value)}},vuex : {getters : {widgetDataArr : (state) => state.widgetDataArr},actions : {changeWidgetData,changeValidation}}}
</script><style lang="stylus">/*...*/
</style>
复制代码
复制代码
/*** editMixin.js* 编辑组件的mixin*/export default {data (){return {//isValid : false
        }},methods : {onValid (){ //验证通过this.isValid = truethis.changeValidation(this.id, true)},onInvalid (){ //验证失败this.isValid = falsethis.changeValidation(this.id, false)}}
}
复制代码
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 第二次割双眼皮的害处

    ...

    2024/4/24 9:52:42
  2. angular的splitter案例学习

    angular的splitter案例学习,都有注释了,作为自己的备忘。 <!DOCTYPE html><html ng-app"APP"><head><meta charset"UTF-8"><title>Angular pane splitter example</title><link type"text/css" rel"…...

    2024/4/21 12:17:53
  3. 一次基于Vue.Js的用户体验优化 (vue drag)

    一.写在前面 半年以前&#xff0c;第一次在项目上实践VueJs,由于在那之前&#xff0c;没有Angular,avalon等框架的实践经验&#xff0c;所以在Vue的使用上&#xff0c;没有给自己总结出更多的经验和体验。随着项目进行和优化改版&#xff0c;无论是新代码的增加还是旧代码&…...

    2024/4/22 11:12:19
  4. AngularJs(九)--自定义指令(三)及选项卡实例和拖拽实例

    link选项 在自定义指令当中是用来进行DOM操作的。 接收四个参数: (1)scope 自定义指令的作用域 (2)element 每一个自定义指令模板的最外层&#xff08;父层&#xff09;的元素 (3)attr 当前标签上的属性 <pre>m1.directive("myTab",function(){return…...

    2024/5/3 17:47:18
  5. 埋线双眼皮取线能取干净吗

    ...

    2024/4/21 3:23:44
  6. vue.js组件化开发实践

    前言 公司目前制作一个H5活动&#xff0c;特别是有一定统一结构的活动&#xff0c;都要码一个重复的轮子。后来接到一个基于模板的活动设计系统的需求&#xff0c;便有了下面的内容。借油开车。 组件化 需求一到&#xff0c;接就是怎么实现&#xff0c;技术选型自然成为了第一…...

    2024/4/22 0:01:40
  7. 拖放排序插件Sortable.js

    介绍 Sortable.js是一款轻量级的拖放排序列表的js插件&#xff08;虽然体积小&#xff0c;但是功能很强大&#xff09;下载地址&#xff1a;https://github.com/RubaXa/Sor...官方DEMO&#xff1a;http://rubaxa.github.io/Sorta... 特点 支持触屏设备和大部分浏览器&#xff0…...

    2024/4/21 8:06:36
  8. 割双眼皮一年了后悔

    ...

    2024/4/21 21:37:43
  9. 最美双眼皮图片

    ...

    2024/4/21 14:53:45
  10. 长春做双眼皮哪里自然

    ...

    2024/4/23 21:00:02
  11. 长春卢立平双眼皮价格

    ...

    2024/4/23 11:03:30
  12. angular参数传递的几种方式

    1.查询字符串的方式 链接 <div *ngFor"let list of item.Books" [routerLink]"[/detail,{bookId:list.Book.Id}]"></div>接受参数代码 this.bookId this.activedrouted.snapshot.params[bookId];2.路由参数的传递方式 链接 gobooklist_cli…...

    2024/4/24 0:40:23
  13. 广东许多人就是伤风感冒影响做双眼皮效果

    ...

    2024/4/23 15:04:06
  14. AngulaJS路由 ui-router 传参

    在这里分享我做的一个使用ui-router 传参的小demo 1.首先第一步设置入口文件index.html&#xff0c;注意加载的顺序&#xff0c;先加载包&#xff0c;再加载自己写的控制器。 <!doctype html> <html lang"en" ng-app"routerApp"><head>&…...

    2024/4/23 21:14:21
  15. angular路由 和 router-outlet

    path 一个用于匹配浏览器地址栏中URL的字符串。 pathMatch prefix:以指定的路径开头; full:与指定路径完全一样。 参考 路由跳转 this.router.navigate([‘url’,参数]); this.router.navigateByUrl(‘url’); router-outlet 参考 如果在浏览器的地址栏中添加 /about &…...

    2024/4/23 19:05:49
  16. Angular2入坑指南——传参

    前两天在群里问了很长时间如何传参的问题&#xff0c;好多人都没有回答&#xff0c;不知道是他们不会&#xff0c;还是不屑于回答&#xff0c;反正就是没人理我。我想&#xff0c;一定会有刚入坑的小伙伴对于如何传参这个问题绞尽脑汁&#xff0c;那么好&#xff0c;你们的福音…...

    2024/4/23 15:09:39
  17. angular router 路由的简单实用

    angular路由的使用 首先要添加了路由服务文件&#xff08;在创建项目的时候会询问&#xff0c;如果当时没有创建俺的话需要手动创建&#xff09; 下边是一个现成的路由文件&#xff0c;可以参考 //app.module.tsimport { NgModule } from angular/core;import { Routes, Rout…...

    2024/4/23 22:44:10
  18. 双眼皮最多 可以祛多少皮

    ...

    2024/4/23 10:09:11
  19. 割双眼皮后吃笋了怎么办

    ...

    2024/4/23 12:20:30
  20. 内双眼皮可以纹美瞳线么

    ...

    2024/4/24 1:29:56

最新文章

  1. 【打赏收款收银台多合一支付收款HTML源码】

    打赏收款收银台多合一支付收款HTML源码 效果图部分源码领取源码下期更新预报 效果图 部分源码 <!DOCTYPE HTML> <html> <head> <title>打赏台</title> <meta name"keywords" content"收银台,个人收款二维码,支付宝在线收款,微…...

    2024/5/6 21:03:32
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/5/6 9:38:23
  3. 【嵌入式开发 Linux 常用命令系列 4.3 -- git add 不 add untracked file】

    请阅读【嵌入式开发学习必备专栏 】 文章目录 git add 不add untracked file git add 不add untracked file 如果你想要Git在执行git add .时不添加未跟踪的文件&#xff08;untracked files&#xff09;&#xff0c;你可以使用以下命令&#xff1a; git add -u这个命令只会加…...

    2024/5/5 8:53:25
  4. Java-运算符

    运算符 Java语言支持如下运算符&#xff1a; 算术运算符&#xff1a;&#xff0c;-&#xff0c;*&#xff0c;/&#xff0c;%&#xff0c;&#xff0c;--复制运算符&#xff1a;关系运算符&#xff1a;>, <, >, <, , !instanceof逻辑运算符&#xff1a;&&…...

    2024/5/5 8:51:26
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/6 9:21:00
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/6 1:40:42
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/4 23:55:01
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57