前端Vue框架学习(未完)
MVVM模型
vueify介绍
所谓vueify,就是使用.vue格式的文件定义组件,一个.vue文件就是一个组件。
在.vue文件定义的组件内容包括3部分:
<style></style>
标签:定义组件样式
<template></template>
标签:定义组件模板
<script></script>
标签:定义组件的各种选项,比如data, methods等。
Vue生命周期
使用Vue的初次操作
创建一个Vue实例
1 2 3 4 5 6
| var c = new Vue({ el: '#box', data : { msg:'welcome' } })
|
完整的html页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <html> <head> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> window.onload=function (ev) { new Vue({ el: '#box', data:{ msg:'hello' } }) }
</script> </head> <body> <div id="box"> {{msg}}
</div> </body> </html>
|
注:template必须有且只能有一个div
常用指令
v-model
一般是放在表单中,实现了双向绑定
1 2 3 4 5 6
| <div id='box'> <input type='text' v-model="msg" > <br> {{msg}} </div>
|
v-model
被称为 Vue 的指令,指令可以用来做很多事,比如用于 if 条件判断的 v-if,用于绑定值的 v-bind、用于绑定监听事件的 v-on 等等,这在以后会接触到。而这个 v-model 指令的作用是将 input 元素 value 属性的值和我们创建的 Vue 对象中 value 的值进行绑定,我们知道 input 有一个 value 属性,它的值会在浏览器显示(例如后面那个 button 按钮的发送),Vue 将这个值绑定后,在 input 中引起的 value 值变化就会实时反映到关联的 Vue 对象,所以会看到下方引用的 也会跟着变化。
v-repeat
===>v-for="变量名 in 数组"
使用变量的话
,使用索引
1 2 3 4 5 6 7
|
<li v-for="value in array">{{value}}</li>
|
v-text
设置标签的文本
1 2 3 4 5 6 7 8 9 10 11
| <div id="app"> <h2 v-text="message"></h2> // 缺点是会将元素内的所有内容都替换成message <h2> {{message}} </h2> // 插值表达式优点是可以拼接加表达式,更灵活 </div>
var app = new Vue({ el: "#app", data:{ message: "Hello" } })
|
v-html
设置标签的innerHTML, 如果设置的是普通文本则效果跟v-text相同,如果是html语法则会被正确解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| // vue3 <div id="example1" class="demo"> <p>使用双大括号的文本插值: {{ rawHtml }}</p> <p>使用 v-html 指令: <span v-html="rawHtml"></span></p> </div> <script> const RenderHtmlApp = { data() { return { rawHtml: '<span style="color: red">这里会显示红色!</span>' } } } Vue.createApp(RenderHtmlApp).mount('#example1') </script>
|
v-on
侦听Dom事件
因为 Vue 并不知道我们点击了按钮,为了让 Vue 监听到我们点击按钮的事件,需要在被点击的元素上绑定一个 click 事件,前面说过绑定事件用 v-on。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| window.onload=function () { new Vue({ el: '#box', data:{ msg:'hello', array:['he','bo','ce'] }, methods :{ //注意是methods , 而不是method show:function () { alert(1); } } }) } <body> <input type="button" value="按钮" v-on:click="show()"> </body>
|
除了能绑定click以外,v-on
还能绑定如下事件:
- click
@click.stop
: 阻止冒泡事件
@click.prevent
: 阻止默认事件
- mousedown
- dblclick
- mouseover
- mouseout
注:v-on:click=func()
可以直接省略写成@click="func()"
v-bind
用于绑定属性(值、类……),跟v-on
一样有省略写法, v-bind:class
–>:class=
1 2 3 4 5 6
| <div id="app"> <input type="text" v-bind:class='{empty: !count}' v-model="value"> ... </div>
|
Vue 会根据 empty 后的表达式 !count 的真假来判断 class 的值是否为 empty,如果为真(即 count = 0 的情况),则 class 的值为 empty,否则为空。
v-for
一般运用在<ul>
, <ol>
上, 如<ul v-for="(item, index) in todoList" :key="index" >
-
1 2 3
| <li v-for="item in arr"> {{ item }} </li>
|
-
1 2 3
| <li v-for="(item, index) in arr"> {{ index, item }} </li>
|
注:在v-for
中必须要有:key
,这个是li的标识符以区别, 一般是设置为索引。
v-if
可以运用在v-for
的单个item下, 如<li v-if="item.done === true">
1 2 3 4 5 6 7
| <ol class="demo-box" v-for="(item, index) in todoList" :key="index"> <li v-if="item.done === false"> <input type="checkbox" @change="changeTodo(index, true)"> <p>{{item.todo}}</p> <a @click="deleteTodo(index, true)">-</a> </li> </ol>
|
注: v-if
是直接的切换结构,所以通过变量来显示显示和隐藏 ==>v-show
是个更好的选择(只能控制一个元素的显示)
v-slot
slot的作用是指定占位符,留下插槽让其他的内容填充进来
1 2 3 4 5 6 7 8 9 10 11
| <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
|
在向具名插槽提供内容的时候,有个等价的写法: 我们可以在一个 <template>
元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
Vue3(其实从2.6开始)中引入了一个新的指令v-slot,用来表示具名插槽和默认插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // slot占位, modal.vue <slot></slot> // slot模板, used.vue <foo v-slot="{ msg }"> {{ msg }} </foo>
// slot占位, modal.vue <slot name="one"></slot> // slot模板, used.vue <foo> <template v-slot:one="{msg}"> {{ msg }} </template> </foo>
|
插槽指令的缩写: 和 v-bind(:attr)和v-on(@func)相似,缩写只有在存在参数时才生效,这就意味着v-slot没有参数时不能使用#xx=yy
。对于默认插槽,可以使用#default来代替v-slot, 如#header="{ msg }"
=>v-slot:header="{ msg }"
注:注意 v-slot 只能添加在 <template>
上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。
template标签
template的作用是模板占位符,可帮助我们包裹元素,但在循环过程当中,template不会被渲染到页面上
1 2 3 4
| <template v-for="(item, index) in list" :key="item.id"> <div>{{item.text}}--{{index}}</div> <span>{{item.text}}</span> </template>
|
常见用途是:
父子组件间通信数据
1、 父向子传递: Props
父组件有数据,需要通过子组件来展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> <Test :list="list"/> <Board/> </div> </template>
export default { name: 'App', components: { HelloWorld, Test }, data(){ return { list : ["he","we","en"] } } }
<template> <ul v-for="l in list" :key="l"> <li>{{ l }}</li> </ul> </template> export default ({ name: "Test", data(){ return { a: 1 } }, props:{ list: Array }, ... })
|
2、子向父传递: $emit
在子组件上增添了数据,需要回传给父组件
Vue实例属性
-
data
- ▲注意区别, new App{}中的data是属性, 但component中的是函数,``data
property in component must be a function
-
methods:{}
-
computed:{}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| computed: { newName: function(){ // 默认的设置是get方法 return this.firstName + this.lastName } // 等价于 newName: { get: function(){ // 通过原有数据产生新数据 return this.firstName + this.lastName } set: function(value){ // 通过新数据修改原有数据 let arr = value.split(" ") this.firstName = arr[0] this.lastName = arr[1] } } }
|
-
watch
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| data(){ x: 15, y: "T-shirt" }, watch: { // 被监听的对象发生变化后执行函数 x: function(){ if (this.x >26 ) { this.y = "半袖" }else if (this.x <= 26 && this.x > 0){ this.y = "T-shirt" }else{ this.y = "None" } } }
|
computed 相 watch 区别
- computed是计算属性,依赖其他属性计算值,并且computed的值有缓存,只有当计算值变化才会返回内容。
- watch,监听的值发生变化就会执行回调,在回调中可以进行一些逻辑操作。所以一般来说需要依赖别的属性来动态获得值的时候可以使用computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用
–>v-show
切换上性能更好, v-if
初次加载性能更好
一个较为完整的栗子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
| <template> <div> <header> <section> <label for="title">ToDoList</label> <input type="text" v-model="todo" @keyup.enter="addTodo" placeholder="添加ToDo" /> </section> </header> <section> <h2>正在进行 <span>1</span> </h2> <ol class="demo-box" v-for="(item, index) in todoList" :key="index"> <li v-if="item.done === false"> <input type="checkbox" @change="changeTodo(index, true)"> <p>{{item.todo}}</p> <a @click="deleteTodo(index, true)">-</a> </li> </ol> <h2>已经完成 <span>{{todoList.length - todoLen}}</span> </h2> <ul v-for="(item, index) in todoList" :key="index" > <li v-if="item.done === true"> <input type="checkbox" @change="changeTodo(index,false)" checked='checked'> <p>{{item.todo}}</p> <a @click="deleteTodo(index,false)">-</a> </li> </ul> </section> <footer>Copyright © 2014 todolist.cn<a @click="clearData()">clear</a></footer> </div> </template>
<script> import * as Utils from '@/utils/utils'
export default { name: 'HelloWorld', props: { msg: String }, data () { return { todo: '', todoList: [], todoLen: 0 } }, methods: { addTodo () { // 等价于 addTodo: function (){ let todoObj = { todo: this.todo, done: false } var tempList = Utils.getItem('todoList') if (tempList) { tempList.push(todoObj) Utils.setItem('todoList', tempList) } else { var tempData = [] tempData.push(todoObj) Utils.setItem('todoList', tempData) } this.todoList.push(todoObj) this.todoLen++ this.todo = '' }, deleteTodo (index, done) { if(done){ this.todoLen-- } this.todoList.splice(index, 1) Utils.setItem('todoList', this.todoList) }, changeTodo (index, done) { if (done) { this.todoLen-- this.todoList[index].done = true Utils.setItem('todoList', this.todoList) } else { this.todoLen++ this.todoList[index].done = false Utils.setItem('todoList', this.todoList) } }, initTodo () { var todoArr = Utils.getItem('todoList') if (todoArr) { for (let i = 0, len = todoArr.length; i < len; i++) { if (todoArr[i].done === false) { this.todoLen++ } } this.todoList = todoArr } },
clearData () { localStorage.clear() this.todoList = [] this.todoLen = 0 } }, mounted () { this.initTodo() } } </script>
<!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
|
B教程:
网络通信axios
axios:
官方推荐使用
教程:https://zhuanlan.zhihu.com/p/266977438
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| ppp(){ axios({ url:'http://123.207.32.32:8000/home/multidata', // params是针对get请求的参数拼接 params:{ type:'pop', page:1 } }).then(res => { console.log(res) }) // 注:axios方法默认返回一个Promise对象,所以在后面可以直接用then处理请求回来的数据 // method:'get' 设置请求的类型,默认为get }
|
GET方法:
1 2 3 4 5 6 7 8 9 10 11
| axios.get( 'http://123.207.32.32:8000/home/multidata', // params是针对get请求的参数拼接 { params:{ type:'pop', page:1} } ).then(res => { console.log(res) })
|
POST方法:
1 2 3 4 5 6 7 8 9 10
| axios.post('/user', { firstName: 'Fred', // 参数 firstName lastName: 'Flintstone' // 参数 lastName }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
|
并发请求
1 2 3 4 5 6 7 8 9 10 11
| function getUserAccount() { return axios.get('/user/12345'); }
function getUserPermissions() { return axios.get('/user/12345/permissions'); } axios.all([getUserAccount(), getUserPermissions()]) .then(axios.spread(function (acct, perms) { // 两个请求现在都执行完成 }));
|
vue中的h函数总结
大家都知道render函数在vue中非常重要,但其实本质上执行渲染工作的是h函数,本质上也就是createElement函数!
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const app = new Vue({ ··· ··· render: h => h(App) }) const app = new Vue({ ··· ··· render:function(createElement){ return createElment(App) } })
|
h函数就是vue中的createElement方法,这个函数作用就是创建虚拟dom,追踪dom变化的。。。
虚拟dom简单来说就是一个普通的JavaScript对象,包含tag,props,children三个属性。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <div id="app"> <p className="text">lxc</p> </div>
{ tag:"div", props:{ id:"app" }, children:[ { tag:"p", props:{ className:"text" }, children:[ "lxc" ] } ] }
|
Good: vue 中的h函数
Vue-router
安装vue-cli和vue-router: 第一个vue程序(html集成vue)只需要script导入vue的js即可,如果是vue工程则通过vue-cli来建立
1.新建vue工程
-
@vue/cli-init
vue init
是vue-cli2.x的初始化方式
vue init
: 需要npm i -g @vue/cli-init
vue init webpack [project-name]
, webpack为模板<generate a project from a remote template>
,然后按照交互信息提示输入即可完成工程创建
- init创建工程时,交互信息中有一个比较有意思的是选择包管理工具: npm、yarn,两者见
-
@vue/cli
会让选择Vue2还是Vue3
vue create router_project
: 需要npm install -g @vue/cli
vue ui
通过UI创建: 需要npm install -g @vue/cli
vue create [project-name]
注:经测试,Vue CLI v4.5.15
会提示选择yarn还是npm;@vue/cli 4.5.13
没提示==>后来发现貌似是平台的区别,linux上会提示选择,win上默认是yarn
Vue CLI 的包名称由vue-cli已经改成了@vue/cli,如果通过vue-cli来构建Vue3项目则需要通过 npm uninstall vue-cli -g
或 yarn global remove vue-cli
卸载它,然后安装@vue/cli,并且@vue/cli安装好后,如果不安装yarn,那么在vue create创建的时候报错ERROR Error: spawn yarn ENOENT
-
npm init vite-app hello-vue
-
Vue3刚发布不久,官方文档中推荐的创建方法之一就是通过脚手架Vite来创建一个vue3项目
需要安装create-vite-app
-
@vitejs/app
npm init @vitejs/app
然后选择project的框架
注:2既可以创建Vue2,也可以创建vue3;3只能创建vue3;在创建速度上,法3比法2快上很多
2.编写vue组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <-- HelloWord.vue --> <template> <div> 你好 </div> </template>
<script> export default ({ name: "Content" }) </script> <-- Content.vue --> <template> <div> 你好 </div> </template>
<script> export default ({ name: "Content" }) </script>
|
3. 编写router
在根目录下创建router文件夹,并创建index.js文件,需要路由的组件全在这里面规定好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
import Vue from "vue" import VueRouter from "vue-router" import Content from "../components/Content.vue" import HelloWorld from "../components/HelloWorld.vue"
Vue.use(VueRouter)
export default new VueRouter({ mode: "history", routes:[ { path: "/hello", name: "Hello", component: HelloWorld }, { path: "/content", name: "Content", component: Content, children: [ { path:'/user/profile', component: Profile }, { path:'/user/userlist', component: Userlist } ] } ] })
|
4. 挂载router-view
app.vue中使用路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <router-link to="/hello">hello</router-link> <router-link to="/content">content</router-link> <router-view></router-view> </div> </template>
<script> export default { name: 'app', } </script>
<style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
|
5.挂载router
将路由信息暴露给Vue实例使用, main.js
1 2 3 4 5 6 7 8 9 10 11 12
| import Vue from 'vue'; import App from './App.vue';
import router from './router';
Vue.config.productionTip = false
new Vue({ el: "#app", router, render: h => h(App), }).$mount('#app')
|
注:按钮点击后跳转,在method的function中可以通过this.$router.push("/hello")
跳转到指定路由。
路由嵌套
在父组件A.vue中进行路由跳转
A.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| <template> <div> <el-container> <el-aside width="200px"> <el-menu :default-openeds="['1', '3']"> <el-submenu index="1"> <template slot="title"><i class="el-icon-message"></i>会员管理</template> <el-menu-item-group> <el-menu-item index="1-1"> <!-- 路径匹配传参:两种方式 --> <!-- <router-link to="/member/level/2">会员等级</router-link> --> <router-link :to="{name: 'MemberLevel', params: {id:3}}">会员等级</router-link> </el-menu-item> <el-menu-item index="1-2">会员积分</el-menu-item> <el-menu-item index="1-3"> <router-link to="/member/list">会员列表</router-link> </el-menu-item> </el-menu-item-group> </el-submenu>
<el-submenu index="2"> <template slot="title"><i class="el-icon-menu"></i>商品管理</template> <el-menu-item-group> <el-menu-item index="2-1">选项1</el-menu-item> <el-menu-item index="2-2">选项2</el-menu-item> </el-menu-item-group> </el-submenu> </el-menu> </el-aside>
<el-container> <el-header style="text-align: right; font-size: 12px"> <el-dropdown> <i class="el-icon-setting" style="margin-right: 15px"></i> <el-dropdown-menu slot="dropdown"> <el-dropdown-item>用户中心</el-dropdown-item> </el-dropdown-menu> </el-dropdown> <span>王小虎</span> </el-header> <!-- 重点是这行, 展示子组件 --> <el-main> <router-view></router-view> </el-main>
</el-container> </el-container>
</div> </template>
<script>
export default ({ name: "Main" }) </script>
|
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import Vue from "vue" import VueRouter from "vue-router" import Main from "../views/Main" import MemberLevel from "../views/Member/MemberLevel" import MemberList from "../views/Member/MemberList"
Vue.use(VueRouter)
export default new VueRouter({ mode: "history", routes: [ { name: "main", path: "/main", component: Main, children: [ { name: "MemberList", path: "/member/list", component: MemberList }, { name: "MemberLevel", path: "/member/level/:id", component: MemberLevel }, ] } ] })
|
MemberLevel.vue
1 2 3 4 5
| <template> <div> 会员等级 ID: {{ $route.params.id }} </div> </template>
|
如果index.js和MemberLevel.vue采用props, 则可以直接写
注: 两种方式跳转:
<route-link to="/member/level/3"></route-link>
<route-link :to="{name: 'MemberLevel', params: {id: 'xxx'}}"></route-link>
-->to加了冒号后,后面可以跟对象
组件重定向
重定向的意思大家都明白,但Vue中的重定向是作用在路径不同但组件相同的情况下
1 2 3 4 5 6 7 8 9 10 11
| { name: "Main", path: "/main/:name", component: Main, ... }, { path: "/goMain/:name", redirect: "/main/:name" }
|
说明:这里定义了两个路径,一个是/main,一个是/goMain,其中/goMain重定向到了/main路径,由此可以看出重定向不需要定义组件;
Main.vue
1 2 3 4 5
| <el-menu-item index="1-3"> <router-link to="/goMain/admin123">回到首页</router-link> </el-menu-item>
<span>{{ $route.params.name }}</span>
|
整合ElementUI
Vue只关注视图层,但没有UI界面, 类似Bootstrap: Bootstrap 是个 css框架,类似jqueryMoblie一样,只需要给标签加class就行了。Bootstrap 需要 .css + .js ,由于依赖jqery,所以需要将jquery.js也导入
ElementUI教程
安装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| vue create <projectName>
npm i element-ui -S
npm install vue-router --save-dev
npm install sass-loader node-sass --save-dev
npm install -g node-gyp npm install --global --production windows-build-tools npm install node-sass --registry=http://registry.npm.taobao.org
|
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import ElementUI from "element-ui"; import 'element-ui/lib/theme-chalk/index.css'; import Vue from 'vue'; import App from './App.vue'; import router from "./router";
Vue.use(ElementUI) Vue.config.productionTip = false
new Vue({ el: "#app", router, render: h => h(App), }).$mount('#app')
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <router-link to="hello">hello</router-link> <router-link to="login">login</router-link> <router-view/> </div> </template>
<script> export default { name: 'App', } </script>
|
Login.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div> <el-form ref="form" :model="form" label-width="80px"> <el-form-item label="活动名称"> <el-input v-model="form.name"></el-input> </el-form-item> <el-form-item label="活动区域"> </el-form-item> </el-form> </div> </template>
<script> export default { name: "Login", data(){ return { form: { name: "" } } } } </script>
|
router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import Vue from "vue" import VueRouter from "vue-router" import HelloWorld from "../components/HelloWorld" import Login from "../views/Login" Vue.use(VueRouter)
export default new VueRouter({ mode: "history", routes: [ { name: "login", path: "/login", component: Login }, { name: "Hello", path: "/hello", component: HelloWorld } ] })
|
Vuex
状态管理库, 可看作是管理会话的数据库
通过在根实例App中注册 store
选项,该 store 实例会注入到根组件下的所有子组件中,且子组件能通过 this.$store
访问到(如果在函数中自带this指针,则可以直接写成store.state.xxx)。
1.创建store文件夹, 定义js文件
在根目录创建store文件夹, 然后创建index.js
关于js定义有两种:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import Vue from "vue" import Vuex from "vuex"
Vue.use(Vuex)
const state = { user : { name: "" } }
const getters = { getUser(state){ return state.user } }
const mutations = { updateUser(state, user){ state.user = user } }
const actions = { asyncUpdateUser(context, user){ context.commit("updateUser", user) } } export default new Vuex.Store({ state, getters, mutations, actions })
|
将对某个对象数据的管理视为一个模块,因此在store文件夹下还需要创建modules文件夹,并创建相应的模块js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import Vue from "vue" import Vuex from "vuex" import user from "./modules/user"
Vue.use(Vuex)
export default new Vuex.Store({ modules:{ user } })
const user = { state :{ user : { name: "" } }, getters :{ getUser(state){ return state.user } }, mutations :{ updateUser(state, user){ state.user = user } }, actions :{ asyncUpdateUser(context, user){ context.commit("updateUser", user) } }, } export default user;
|
2. 导入main.js中
3. 使用vuex
工具
附录:
什么是Vue.use()
?
Vue实例通过Vue.use()
来装载插件,从而用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:
- 添加全局方法或者 property。如:vue-custom-element
- 添加全局资源:指令/过滤器/过渡等。如 vue-touch
- 通过全局混入来添加一些组件选项。如 vue-router
- 添加 Vue 实例方法,通过把它们添加到
Vue.prototype
上实现。
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router
注:通过全局方法 Vue.use()
使用插件。它需要在你调用 new Vue()
启动应用之前完成:
Vue.js 的插件应该暴露一个 install
方法,即XxxxPlugin.install = function (Vue, options) {}
。这个方法的第一个参数是 Vue
构造器,第二个参数是一个可选的选项对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或 property Vue.myGlobalMethod = function () { // 逻辑... }
// 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... })
// 3. 注入组件选项 Vue.mixin({ created: function () { // 逻辑... } ... })
// 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... } }
|
Q: 为什么axios不用?
Q:相信很多人在用Vue使用别人的组件时,会用到 Vue.use()
。例如:Vue.use(VueRouter)
、Vue.use(MintUI)
。但是用 axios
时,就不需要用 Vue.use(axios)
,就能直接使用。那这是为什么呐?
A:因为 axios
没有 实现install
方法,即axios并不是Vue插件,他并不是特地为Vue服务的网络通信库,而是通用的库。
所以为了Vue实例能够全局使用axios,采取的方法是将axios加到Vue实例的原型中,只不过这样在组件中使用时需要通过this.$axios.func
来调用(最合理的调用),Q: this.$axios和this.axios和axios有什么区别?
参考:https://www.jianshu.com/p/89a05706917a——如何定义一个自己的Vue插件
Vue实例里this的使用
这是vue文档里的原话:All lifecycle hooks are called with their ‘this’ context pointing to the Vue instance invoking it.
意思是:在Vue所有的生命周期钩子方法(如created,mounted, updated以及destroyed)里使用this,this指向调用它的Vue实例。
- 在Vue所有的生命周期钩子方法
- methods(普通方法)中使用的this指向为创建的vue组件实例。
- vue组件生命周期钩子方法里面还有方法使用this: 在函数内部使用this时,this会指向window,而非vue实例
- vue箭头方法中使用this: 箭头函数没有自己的this, 它的this是继承而来; 默认指向在定义它时所处的对象(宿主对象),而不是执行时的对象, 定义它的时候,可能环境是window;
- data中的this指向为windows。
参考:vue中this的指向——比较全
template的组件中ref作用
可以获得相应的html元素,效果跟document.getElementById相同,获得以后就可以调用其元素的一些功能,如focus啥的
好应用案例
https://juejin.cn/post/6888604279679451143)
JS函数
普通函数
1 2 3 4 5
| var x = myFunction(7, 8);
function myFunction(a, b) { return a * b; }
|
箭头函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| x => x * x function (x) { return x * x; }
(x, y) => x * x + y * y
() => 3.14
x => { foo: x }
x => ({ foo: x })
|
箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this
是词法作用域,由上下文确定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| var obj = { birth: 1990, getAge: function () { var b = this.birth; var fn = function () { return new Date().getFullYear() - this.birth; }; return fn(); } };
var obj = { birth: 1990, getAge: function () { var b = this.birth; var fn = () => new Date().getFullYear() - this.birth; return fn(); } }; obj.getAge();
|
匿名函数
匿名函数的使用场景比较多,是简化了代码编写的一种手段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| var obj={ name:"张培跃", age:18, fn:function(){ return "我叫"+this.name+"今年"+this.age+"岁了!"; } };
var fn=function(){ return "我是一只小小小小留下,怎么飞也飞不高!" }
setInterval(function(){ console.log("我其实是一个回调函数,每次1秒钟会被执行一次"); },1000);
function fn(){ return function(){ return "张培跃"; } }
|
匿名函数的作用:
1、通过匿名函数可以实现闭包,关于闭包在后面的文章中会重点讲解。在这里简单介绍一下:闭包是可以访问在函数作用域内定义的变量的函数。若要创建一个闭包,往往都需要用到匿名函数。
2、模拟块级作用域,减少全局变量。执行完匿名函数,存储在内存中相对应的变量会被销毁,从而节省内存。再者,在大型多人开发的项目中,使用块级作用域,会大大降低命名冲突的问题,从而避免产生灾难性的后果。自此开发者再也不必担心搞乱全局作用域了。
JS成员方法
写Vue的时候总是不知道什么时候用xxx: {}
, 什么时候用xxx(){}
。本身这个问题并不难,问题就在于一知半解,所以本小节就是专门进行说明的
1 2 3 4 5 6 7 8 9 10
| const v = { name: "cl", data(){ console.log("data") }, say: function(){ console.log("say") } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
var Student1=new Object(); Student1.name="方式一 爱尔得华"; Student1.age=23; Student1.toString1=function(){ alert("方式一 hello my name is toString1()"); }
Student1.toString=function(){ var result="方式一 在toString方法内输出信息:年龄:" + this.age + ",姓名:" + this.name; alert(result);
return result="方式一 谁调用toString方法就返回给谁输出这句话 重写方法内容"; } alert(Student1); Student1.toString1();
var Student2={ name: "方式二 爱尔得华2", age: 29, toString1: function(){ alert("方式二 my name is Student2 toString1()"); } },
|
编写注意点:
如果是在函数体内写的语句是不需要分号分隔的,如
1 2 3 4 5 6 7 8 9 10
| setup(){ onMounted(()=>{ }) functipon xxx(){ } return {} }
|
与之对应的是在对象体内,如
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var app = { data() { }, methods:{ }, props:{ }, mounted(){ console.log("xxx") } }
|
里面会出现键值对和必须要逗号分隔开
【Vue】详解 SFC 与 vue-loader
vue-loader 是一个Webpack
的 loader,使用 vue-loader 就可以用 Vue Single-File Component (SFC) 即单文件组件的形式编写一个组件,vue-loader 会将.vue
文件转换为 JS模块。
.vue 单文件组件 (SFC) 规范
<template>
模板块
一个SFC中最多一个< template >块;
其内容将被提取为字符串传递给 vue-template-compiler ,然后webpack将其编译为js渲染函数,并最终注入到从 <script>
导出的组件中;
<script>
脚本块
一个SFC最多一个<script>
块;
它的默认导出应该是一个 Vue.js 的组件选项对象,也可以导出由 Vue.extend() 创建的扩展对象。
思考:Vue.extend() 中 data 必须是函数,所以在.vue SFC的script中,export中的data是函数
<style>
样式块
一个 .vue 文件可以包含多个 <style>
标签;
可以使用scope和module进行封装;
具有不同封装模式的多个<style>
标签可以在同一个组件中混合使用;
.env配置文件
Vue-cli创建的
使用vue-cli3构建的项目就简单多了,因为vue-cli3使用上述的DefinePlugin方式帮你把process.env.NODE_ENV配置好了,我们不需要再自己去配置。它自带了三种模式:
- development:在vue-cli-service serve下,即开发环境使用
- production:在vue-cli-service build和vue-cli-service test:e2e下,即正式环境使用
- test: 在vue-cli-service test:unit下使用
1 2 3 4 5 6 7 8 9 10 11
| { "name": "", "version": "0.1.0", "private": true, "scripts": { "dev": "vue-cli-service serve", "build": "vue-cli-service build", }, "dependencies": { } }
|
对应的.env
如下:
.env.development
.env.test
.env.production
如果自己写package.json
中的scripts命令的话应该如下写
1 2 3 4 5 6 7
| "scripts": { "dev": "vue-cli-service serve --mode development", "test": "vue-cli-service build --mode test", "prod": "vue-cli-service build --mode production", },
|
程序中通过process.env
来获取
1 2 3 4 5 6 7
|
const option = { baseURL: process.env.NODE_ENV == "development" ? "http://192.168.31.172:5501/api/v1" : "http://devops4.sucsoft.com:30383/api/v1", timeout: 5000 }
|
注: .env
中的文件需要以VUE_APP_
为前缀才能被检测到, 如.env.production
中填写VUE_APP_BASE_URL="hhh"
,之后可以在项目中通过process.env.VUE_APP_BASE_URL
来获取
此外,还可以通过添加--mode
参数自定义指定要加载的.env
文件
1 2 3
| "scripts": { "vvv": "vue-cli-service serve --mode vvv" },
|
搭配.env.vvv
的配置文件
Vite
.env文件
1 2 3 4
| .env # 所有情况下都会加载 .env.local # 所有情况下都会加载,但会被 git 忽略 .env.[mode] # 只在指定模式下加载,mode可为production,development或其他自定义值。 .env.[mode].local # 只在指定模式下加载,但会被 git 忽略
|
1
| VITE_APP_WEB_URL=http://baidu.com
|
注:前缀必须为VITE_APP_
,并且通过console.log(import.meta.env)
来获得
- 在页面中使用
console.log(import.meta.env.VITE_APP_WEB_URL)
- 在 package.json中定义
1 2 3 4 5
| "scripts": { "dev": "vite", "build": "vite build", "prod": "vite --mode prod" },
|
说明:执行npm run prod
时, 既先会加载.env
的内容,然后加载.env.prod
的内容
src/views
和src/
components`区别
两者的区分更多的是一种惯例,实际上两者定义的都是视图组件;如果要说区别的话,最大的区别在于用途和重用程度上:
- 可重用的组件内容可以保存在src / components文件夹中(诸如广告,网格或任何自定义控件之类的示例,如样式化的文本框或按钮。再比如页眉,页脚等页面结构性质的可重用组件,还可以创建
src/layouts/
文件夹)
- 与路由器绑定的内容,可以保存在src / views中(作页面的组件,路由到类似页面进行导航)
即views的组件多一层充当路由组件的功能:
在Vue中(通常是Vue Router)处理路由时,将定义路由以切换组件中使用的当前视图<router-view>
。这些路线通常位于src/router/routes.js
,我们可以看到以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Home from '@/views/Home.vue' import About from '@/views/About.vue'
export default [ { path: '/', name: 'home', component: Home, }, { path: '/about', name: 'about', component: About, }, ]
|
位于src/components
下里的组件不太可能在一条路线中使用,而位于src/views
将被至少一条router路线使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| const routes = [{ path: '/', name: 'Index', key: 'Index', redirect: '/dashboard/index', hidden: true, label: "首页", icon: renderIcon(BookIcon), }, { path: '/dashboard', name: 'Dashboard', key: 'Dashboard', redirect: '/dashboard/index', component: Home, label: "首页", icon: renderIcon(BookIcon), meta: { label: "首页", }, children: [{ path: "/dashboard/index", name: 'DashboardIndex', key: 'DashboardIndex', component: () => import('../views/HelloWorld.vue'), label: "Hello", icon: renderIcon(BookIcon), meta: { label: "Hello", } }, { path: "/dashboard/page1", name: 'DashboardPage1', key: 'DashboardPage1', component: () => import('../views/Page1.vue'), label: "第一页", icon: renderIcon(PersonIcon), meta: { label: "第一页", } }] }, { path: "/users", name: 'Users', key: 'Users', redirect: '/users/index', component: Home, label: "账户", icon: renderIcon(WineIcon), meta: { label: "账户", }, children: [{ path: '/users/index', name: 'UsersIndex', key: 'UsersIndex', component: () => import('../views/Page2.vue'), label: "第二页", icon: renderIcon(WineIcon), meta: { label: "第二页", }, }], }, ]
|
Vue部署
1 2 3 4 5 6 7 8 9 10 11 12 13
| FROM node:10 AS build-env WORKDIR /app COPY ./package.json . RUN npm install --registry=http://registry.npm.taobao.org COPY . . RUN npm run build
FROM nginx WORKDIR /usr/share/nginx/html ENV TZ=Asia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone COPY --from=build-env /app/dist /usr/share/nginx/html/ EXPOSE 80
|
from : https://github.com/liguobao/58HouseSearch/blob/master/House-Map.UI/Dockerfile