vue router:用来做路由
vuex:用来管理
mvvm:model-view-viewmodel
cdn:内容分发网络
vue里的方法必须写到methods里面
Object.defineProperty(对象名,‘x’,操作对象):在一个对象上定义或修改新的值
操作对象中的get方法在调用obj.x时会自动执行,set方法在obj.x = 来赋值时会自动执行
特性检测:
检测是否支持圆角和placeholder
检测是否是触屏设备
Vue底层原理
MVVM
MVVM是Model-View-ViewModel的简写,即模型-视图-视图模型。Model指的是后端传递的数据。View指的是所看到的页面。ViewModel是mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:
1. 将Model转化成View,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
2. 将View转化成Model,即将所看到的页面转化成后端的数据。实现的方式是:DOM事件事件监听。
3. 这两个方向都实现的,我们称之为数据的双向绑定。
模板字符串template
不能写if语句,{{}}中只能是表达式
vue指令
只有写在Vue实例的el内的元素才会识别vue指令
-
v-text:v-text可以用于替换插值表达式,如果有v-text,那么插值不起作用
注 vue的指令都是v-指令这种格式,一旦使用了指令,虽然我们写的值看起来像字符串,因为我们使用的"值"已经是JavaScript的代码了,所以会报错
错误写法:<div v-text=“这里是v-text的内容”>
正确写法:<div v-text=" ‘这里是v-text的内容’ ">
v-html:正确输出带有html标签的内容
注 如果是富文本编辑器的内容,直接渲染的话,是被转义过的,那么html标签就会直接被渲染到页面,要正确输出正确的不带html标签转义的内容就要使用v-html指令 -
v-cloak:直接加在标签里面,可以设置css样式[v-cloak] { display: none;},这个样式只有在vue实例化之前才有效,实例化之后会自动失效。可以用来防止页面加载时闪烁出现vue标签或指令的问题
-
v-for:循环渲染数据,v-for="(item[,index]) in list",其中list为vue中data里的对象名(数组名),index会自动渲染list里的键名,item会自动渲染键值。
注:v-for是循环自身所在的标签 -
v-if: v-if=“判断式”,判断式为true就显示此条,否则不显示。其中判断式的左值在vue里定义。
v-show:和v-if用法相似,但v-show是通过style的display来控制显示隐藏,而v-if是直接把该节点移除或插入。对于需要频繁切换显示和隐藏的节点v-show比较实用 -
v-on:v-on:事件=“赋值表达式”,绑定事件监听器,这个事件里可以直接操作data里的数据,可以和v-show/if配合使用。事件里也可以直接调用methods的方法
注:v-on:事件可以缩写为@事件
用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件。 -
v-bind:v-bind:属性,用于动态绑定元素的属性,绑定了才能从data中获取值。v-bind:属性可以简写为:属性。
绑定用户输入:
<body>
<div id="root">
<input type="text" :value="inputValue" @input="handleInput" />
<p>您输入的内容是:{{inputValue}}</p>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#root",
data: {
inputValue: ''
},
methods: {
handleInput (e) {
//input事件里的e.target可以获取输入框
this.inputValue = e.target.value
}
}
})
</script>
</body>
- v-model:绑定用户输入(<input type=“text” v-model="inputValue />,data:{inputValue: ‘’})。可以实现双向绑定,内容会根据改动而更新
<body>
<div id="root">
<input type="text" v-model="inputValue" />
<p>您输入的内容是:{{inputValue}}</p>
<input type="checkbox" v-model="hobi" value="打球">打球
<input type="checkbox" v-model="hobi" value="被球打">被球打
<input type="checkbox" v-model="hobi" value="被球员打">被球员打
<p>你的爱好是:{{hobi}}</p>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#root",
data: {
inputValue: '',
hobi: ['打球']
}
})
</script>
</body>
-
vue的事件:不加括号,默认事件处理函数接收event对象,加括号就可以传递想要的值
-
按键修饰符:@keyup.enter:敲击回车触发。可以定义组合键,如:@keyup.ctrl.enter(同时敲击ctrl和回车);.prevent阻止默认事件
@click.stop:阻止事件冒泡
@click.prevent:阻止事件的默认行为
-
样式绑定:class的样式可以通过true和false来控制(<div :class="{a: true,b: false}">,其中a和b都是定义的style)
-
map方法:创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
filter方法:创建一个新数组,其结果是该数组中通过筛选条件的元素
计算属性&监视&过滤器
computed:vue里的计算属性,和methods类似,有依赖缓存,只有它依赖的数据发生了改变才会重新计算,必须要有返回值。在插值表达式中调用computed的方法会直接在页面中打印方法的返回值。
可以这样理解,computed并不是一个方法,而是依赖于属性的,就是一个属性的封装,属性的值不变化,那么不会多次调用computed,所以性能更好
计算属性默认只有getter,需要时也可以提供setter。当调用这个计算属性就会自动调用get,赋值的时候就会自动调用set(value),其参数为赋的值
methods: 这是方法,每次有数据更改,只要在模板里有使用这个方法,这个方法就会执行。它是没有缓存的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>simple todolist</title>
<link href="https://cdn.bootcss.com/bulma/0.7.4/css/bulma.min.css" rel="stylesheet">
</head>
<body>
<div id="root">
<section class="hero is-primary">
<div class="hero-body">
<div class="container">
<h1 class="title">
待办事项列表
</h1>
<h2 class="subtitle">
完成今天的事情,明天就可以做更多事情。老板的任务是做不完的。提高效率没有用
</h2>
</div>
</div>
</section>
<section class="hero">
<div class="container" style="margin-top: 15px">
<div class="field has-addons">
<div class="control">
<input
@keyup.enter="addTodo"
v-model="inputValue"
class="input"
type="text"
placeholder="请输入待办事项"
>
</div>
<div class="control">
<a @click="addTodo" class="button is-info">
添加
</a>
</div>
<div class="control">
总共有{{totalTodosCount}}个任务,其中未完成{{unCompletedTodosCount}}个,已完成{{completedTodosCount}}个
</div>
</div>
<h4>未完成</h4>
<div class="columns is-multiline is-mobile">
<div
class="column is-one-quarter"
v-for="todo in unCompletedTodos"
:key="todo.id"
>
<div class="card">
<div class="card-content">
<div class="content">
{{todo.title}}
</div>
</div>
<footer class="card-footer">
<span
:class="{
'card-footer-item': true,
'has-text-primary': todo.hasCompleted,
'has-text-danger': !todo.hasCompleted
}"
>
{{ todo.hasCompleted ? '已完成' : '未完成'}}
</span>
<span
class="card-footer-item"
@click="toggleCompleted(todo.id)"
>
标记为{{ todo.hasCompleted ? '未' : '已'}}完成
</span>
</footer>
</div>
</div>
</div>
<h4>已完成</h4>
<div class="columns is-multiline is-mobile">
<div
class="column is-one-quarter"
v-for="todo in completedTodos"
:key="todo.id"
>
<div class="card">
<div class="card-content">
<div class="content">
{{todo.title}}
</div>
</div>
<footer class="card-footer">
<span
:class="{
'card-footer-item': true,
'has-text-primary': todo.hasCompleted,
'has-text-danger': !todo.hasCompleted
}"
>
{{ todo.hasCompleted ? '已完成' : '未完成'}}
</span>
<span
class="card-footer-item"
@click="toggleCompleted(todo.id)"
>
标记为{{ todo.hasCompleted ? '未' : '已'}}完成
</span>
</footer>
</div>
</div>
</div>
</div>
</section>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#root',
data: {
inputValue: '',
todos: [{
id: 1,
title: '吃饭',
hasCompleted: true
}, {
id: 2,
title: '再吃一次饭',
hasCompleted: false
}]
},
computed: {
totalTodosCount () {
console.log('total')
return this.todos.length
},
completedTodosCount () {
return this.todos.filter(todo => todo.hasCompleted === true).length
},
completedTodos () {
return this.todos.filter(todo => todo.hasCompleted === true)
},
unCompletedTodosCount () {
return this.totalTodosCount - this.completedTodosCount
},
unCompletedTodos () {
return this.todos.filter(todo => todo.hasCompleted !== true)
}
},
methods: {
addTodo () {
this.todos.push({
id: Math.random(),
title: this.inputValue,
hasCompleted: false
})
this.inputValue = ''
},
toggleCompleted (id) {
this.todos = this.todos.map(todo => {
if (todo.id === id) {
todo.hasCompleted = !todo.hasCompleted
}
return todo
})
}
}
})
</script>
</body>
</html>
watch:监视已有的元素发生改变时会自动执行方法
注:computed是新生成一个数据,wacth是观测已有的数据,当被观测的数据发生改变的时候,后面的方法会自动执行
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch
watch与computed的区别
1.watch监控现有的属性,computed通过现有的属性计算出一个新的属性
2.watch不会缓存数据,每次打开页面都会重新加载一次,
但是computed如果之前进行过计算他会将计算的结果缓存,如果再次请求会从缓存中
得到数据(所以computed的性能比watch更好一些)
下面这段代码摘自 https://blog.csdn.net/soullines/article/details/80449321
### watch
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
### computed
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
filters:过滤器可以在插值表达式或v-bind里加上 | 来实现过滤,过滤方法传入的参数为当前需要过滤的数据,返回过滤结果
例:
<body>
<div id="app">
{{name | firstToUpperCase}}
<!-- 这里的sum调用了计算属性 -->
{{sum | tofix}}
{{orderStatus | formatOrderStatus}}
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
name: 'leo',
x: 0.1,
y: 0.2,
orderStatus: '001'
},
filters: {
firstToUpperCase(v) {
console.log(v);
return v.slice(0, 1).toUpperCase() + v.slice(1)
},
tofix(v) {
console.log(v);
return v.toFixed(2)
},
formatOrderStatus(v) {
console.log(v);
const orderStatusMap = {
'001': '未付款',
'002': '已付款',
'003': '已发货',
'004': '已签收',
'005': '已评价'
}
return orderStatusMap[v]
}
},
computed: {
sum() {
return this.x + this.y
}
}
})
</script>
</body>
computed、watch、filter总结
watch:监控已有属性,一旦属性发生了改变就去自动调用对应的方法
computed:监控已有的属性,一旦属性的依赖发生了改变,就去自动调用对应的方法
filter:js中为我们提供的一个方法,用来帮助我们对数据进行筛选
ajax:在created()里面用fetch方法做
axios:需要重新引入,也是用来发送ajax请求。在Vue这个对象的原型上扩展一个$http 让它指向于axios, 那么在实例上就可以直接使用this.$http来使用axios了
<div id="app">
<ul>
<li v-for="todo in todos" :key="todo.id">{{todo.title}}</li>
</ul>
</div>
<script src="./vue.js"></script>
<script src="./axios.min.js"></script>
<script>
// 在Vue这个对象的原型上扩展一个$http 让它指向于axios, 那么在实例上就可以直接使用this.$http来使用axios了
Vue.prototype.$http = axios
const app = new Vue({
el: '#app',
data: {
todos: []
},
created() {
this.$http.get('https://jsonplaceholder.typicode.com/todos')
.then(resp => {
// 这里是和后端约定好的一个状态,可以是任何的key, 只要约定好就行。
if (resp.status === 200) {
// 这里才是成功的逻辑
this.todos = resp.data
} else {
// 处理错误的逻辑
}
})
.catch(err => {
// 处理错误的逻辑
})
}
})
</script>
组件
可以自定义html标签,用来展示更多的内容
组件配置项的模板只能有一个根元素,template可以写到script标签外面<template>渲染内容</template>
一般用局部组件
注:当定义组件的名字为 kebab-case (短横线分隔命名)时,html中引用这个组件只能用kebab-case;当定义组件的名字为PascalCase (首字母大写命名)时,html中引用这个组件可以用kebab-case也可以用PascalCase
组件配置项:定义组件的执行内容,一般都用template渲染。可以定义组件的data方法(只能是方法),return一个对象,这样才能保证每个组件的数据是独立的而不是共享的。
component
全局组件
**Vue.component(组件名,组件配置项)**全局注册组件,定义的每个vue实例都可以引用这些组件。组件配置项可以放在外面定义,其内容会渲染组件。
components
局部组件
在vue里面定义一个components: {组件名:组件配置项},只有当前的Vue示例中才能使用这些组件
组件嵌套
一个组件的配置项里面可以嵌套另一个组件
注:内层组件只能在外层组件的渲染模板里面使用,而且外层组件的template中必须要有一个唯一的根元素
props组件通信
传递非字符串类型的数据,需要使用动态绑定属性 v-bind:
以下描述的自定义属性并非真正的自定义属性,只是定义方式和定义位置和自定义属性类似
写法一:组件配置项里面加上props:[‘属性名’],其中的属性名是接收的组件里的自定义属性,将这个属性名作为插值表达式写入template里。
注:组件的自定义属性名为props定义的需要在组件配置项中用到的,属性值为vue实例里的内容(data或computed等等),这也是父组件向子组件传值的方式
<body>
<div id="app">
<!-- 在调用这个组件的时候通过text属性绑定一个值,在组件内部就可以通过props来接收这个值 -->
<my-text :text="text1"></my-text>
<my-text text="world"></my-text>
<my-text text="!"></my-text>
</div>
<script src="./vue.js"></script>
<script>
const MyText = {
template: '<span>{{text}}</span>',
// 通过props来接收调用的时候传过来的值。一旦接收了,就可以直接把props当data用,但是不能直接在内部修改props的值。这是基于单向数据流的原则的。
props: ['text']
}
const app = new Vue({
el: '#app',
data: {
text1: 'hello',
text2: 'world',
text3: '!'
},
components: {
MyText
}
})
</script>
</body>
写法二:有时希望每个 prop 都有指定的值类型,可以以对象形式列出 props,这些属性的名称和值分别是 prop 各自的名称和类型
template: '<div>{{x}} + {{y}} = {{x + y}}</div>',
// 如果要对传入的props进行类型检查,就需要使用对象的方式来写props
props: {
// 如果要对x进行更多约束,就可以再把这个x写成一个对象,里面可以有默认值,必须这些选项
x: {
//类型为number,如果传入的类型为string会报错
type: Number,
//必传就required: true
required: true
},
y: {
type: Number,
//不需要传就设置默认值
default: 0
}
}
注:组件的自定义属性中,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post>
自定义事件
点击的时候先执行父组件下面的某一个方法,通过$emit和on做了中转
$emit(事件名,传递参数):事件名即为组件中的自定义事件名,这个事件调用的方法在父组件中执行,方法需要的参数即为子组件的emit中传递的参数
流程:子组件上触发事件的方法在子组件的methods中执行,子组件的方法通过$emit引用子组件的自定义事件而触发父组件对应的方法,所以实际上是调用的父组件的方法,因为数据在哪里方法就在哪里。
父子组件通信:父组件通过props传递到子组件,子组件通过自定义事件传递到父组件,父组件调用子组件的同时注册一个监听(v-on),最终的数据修改在父组件里面
注
1.Vue创建的实例可以看做一个组件
2.父组件向子组件用子组件的自定义属性来传值
<body>
<div id="app">
<!-- 在调用这个组件的时候,咱们就通过 @事件名 这种方式去监听组件内部触发的自定义事件, 当内部有这个自定义事件触发的时候,就会执行app里的onChangeText方法 -->
<hello :hello-text="text" @change-text="onChangeText"></hello>
</div>
<script src="./vue.js"></script>
<script>
const Hello = {
template: '<div>{{helloText}} <button @click="changeHelloText">改文字</button></div>',
props: ['helloText'],
methods: {
changeHelloText () {
// 如果我在这里直接写this.helloText = 'xxxx'就会报错,因为单向数据流的原因。
// 如果要修改的话,就得找到data定义的地方,在这个例子里面是父级。
// 我们就需要在这个组件的按钮点击事件上,通过this.$emite触发一个自定义事件
this.$emit('change-text', Math.random().toString())
}
}
}
const app = new Vue({
el: '#app',
methods: {
onChangeText (text) {
// 最终的数据修改在父组件里面
this.text = text
}
},
data: {
text: 'hello world!!!'
},
components: {
Hello
}
})
</script>
</body>
单向数据流
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
即子组件的方法不能修改父组件里的数据
插槽
作用:可以在自定义组件里面插入其它标签
slot 组件配置项里的slot标签需要有name属性,这个name的值对应组件里带有slot属性的标签且其值相等,当组件渲染的时候slot标签就会替换成相应的标签
const TodoHeader = {
template: '<slot name="before"></slot>'
}
<todo-header>
<h1 slot="before" class="title">待办事项列表</h1>
</todo-header>
组件化todolist
<body>
<!-- todo-header组件的模板 -->
<template id="todoHeader">
<section class="hero is-primary">
<div class="hero-body">
<div class="container">
<slot name="before"></slot>
<h2 class="subtitle">
完成今天的事情,明天就可以做更多事情。老板的任务是做不完的。提高效率没有用
</h2>
<slot name="after"></slot>
</div>
</div>
</section>
</template>
<!-- todo-input组件的模板 -->
<template id="todoInput">
<div class="field has-addons">
<div class="control">
<input
ref="myInput"
@keyup.enter="addTodo"
v-model="inputValue"
class="input"
type="text"
placeholder="请输入待办事项"
>
</div>
<div class="control">
<a @click="addTodo" class="button is-info">
添加
</a>
</div>
</div>
</template>
<!-- card组件的模板 -->
<template id="card">
<div class="column is-one-quarter">
<div class="card">
<div class="card-content">
<div class="content">
{{title}}
</div>
</div>
<footer class="card-footer">
<span :class="{
'card-footer-item': true,
'has-text-primary': hasCompleted,
'has-text-danger': !hasCompleted
}">
{{ hasCompleted ? '已完成' : '未完成'}}
</span>
<span class="card-footer-item" @click="toggleCompleted">
标记为{{ hasCompleted ? '未' : '已'}}完成
</span>
</footer>
</div>
</div>
</template>
<!-- 以下html可以理解为最外层的组件的模板 -->
<div id="root"> <!-- 实例的挂载点 -->
<!-- 使用todo-header, 它是没有props的,所以就直接用 -->
<todo-header>
<h1 slot="before" class="title">待办事项列表</h1>
<h3 slot="after">Lorem ipsum dolor sit amet consectetur adipisicing elit. Sit non omnis, officia ratione perspiciatis ipsa minus dignissimos neque aliquam nobis ipsam consequatur, facere sunt quasi corrupti architecto quis eum sequi.</h3>
</todo-header>
<!-- 这些正常的html标签,不用管,你怎么写都行 -->
<section class="hero">
<div class="container" style="margin-top: 15px">
<!-- 这是使用todo-input这个组件,由于这个组件内部会emit一个事件叫做add-todo, 所以我们要在这里创建监听, 当内部$emit('add-todo', 参数)的时候,就会执行这个模板所对应的组件(Vue实例)里的方法叫addTodoToData-->
<todo-input @add-todo="addTodoToData"></todo-input>
<h4>未完成</h4>
<div class="columns is-multiline is-mobile">
<!-- 这里使用card组件,card组件需要接收多个props,id, title, has-completed(html写法)在内部用hasCompleted来接收, 注意,这里是循环,需要有一个key, 还有就是,动态绑定 -->
<!-- 内部还会$emit一个toggle-completed-by-id的自定义事件 -->
<card
v-for="todo in unCompletedTodos"
:key="todo.id"
:id="todo.id"
:title="todo.title"
:has-completed="todo.hasCompleted"
@toggle-completed-by-id="toggleCompletedById"
></card>
</div>
<h4>已完成</h4>
<div class="columns is-multiline is-mobile">
<card
v-for="todo in completedTodos"
:key="todo.id"
:id="todo.id"
:title="todo.title"
:has-completed="todo.hasCompleted"
@toggle-completed-by-id="toggleCompletedById"
></card>
</div>
<div class="control">
总共有{{totalTodosCount}}个任务,其中未完成{{unCompletedTodosCount}}个,已完成{{completedTodosCount}}个
</div>
</div>
</section>
</div>
<script src="./vue.js"></script>
<script>
// 这个组件最简单
const TodoHeader = {
template: '#todoHeader'
}
const TodoInput = {
template: '#todoInput',
data() {
return {
// 由于咱们不需要每次输入的时候都向外发送数据,所以这个inputValue就放在组件内部
inputValue: ''
}
},
methods: {
// 当在组件内部点击添加的时候,会执行这个方法,当这个方法执行的时候,会向外发送一个add-todo的自定义事件,并且把this.inputValue传递出去
addTodo() {
this.$emit('add-todo', this.inputValue)
this.inputValue = ''
this.$refs.myInput.focus()
}
}
}
const Card = {
template: '#card',
props: {
title: String,
id: Number,
hasCompleted: Boolean
},
methods: {
toggleCompleted () {
this.$emit('toggle-completed-by-id', this.id)
}
}
}
const app = new Vue({
el: '#root',
components: {
TodoHeader,
TodoInput,
Card
},
data: {
todos: [{
id: 1,
title: '吃饭',
hasCompleted: true
}, {
id: 2,
title: '再吃一次饭',
hasCompleted: false
}]
},
computed: {
totalTodosCount() {
console.log('total')
return this.todos.length
},
completedTodosCount() {
return this.todos.filter(todo => todo.hasCompleted === true).length
},
completedTodos() {
return this.todos.filter(todo => todo.hasCompleted === true)
},
unCompletedTodosCount() {
return this.totalTodosCount - this.completedTodosCount
},
unCompletedTodos() {
return this.todos.filter(todo => todo.hasCompleted !== true)
}
},
methods: {
addTodoToData(todoTitle) {
this.todos.push({
id: Math.random(),
title: todoTitle,
hasCompleted: false
})
},
toggleCompletedById(id) {
this.todos = this.todos.map(todo => {
if (todo.id === id) {
todo.hasCompleted = !todo.hasCompleted
}
return todo
})
}
}
})
</script>
</body>
兄弟组件通过中间的来通信
在vue里推荐使用ref的方式来获取dom或者组件,这样就可以使用this.$refs.name取到那个tag,<tag ref=“name”></tag>
ref 被用来给DOM元素或子组件注册引用信息。引用信息会根据父组件的 $refs 对象进行注册。如果在普通的DOM元素上使用,引用信息就是元素; 如果用在子组件上,引用信息就是组件实例
dynamic-component(动态组件)
component组件:<conponent is=“组件名”></component>,component组件就会替换成相应的组件内容
<body>
<div id="app">
<ul>
<li @click="currentComponent = 'guo-nei'">国内新闻</li>
<li @click="currentComponent = 'guo-ji'">国际新闻</li>
</ul>
<component :is="currentComponent"></component>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
currentComponent: 'guo-nei'
},
components: {
'guo-nei': {
template: '<div>国内新闻的页面</div>'
},
'guo-ji': {
template: '<div>international news</div>'
}
}
})
</script>
</body>
is可以实现语义化标签和解决标签不合法的情况