vue基础
历史介绍
- angular 09年,年份较早,一开始大家是拒绝 star:
- react 2013年, 用户体验好,直接拉到一堆粉丝 star:
- vue 2014年, 用户体验好 作者:尤雨溪 江苏无锡人 国人骄傲
前端框架与库的区别?
- jquery 库 => DOM(操作DOM) 请求
- 有可能学习了一些art-template 库 -> 模板引擎
- 框架
- 全方位功能齐全
- 简易的DOM体验 发请求 模板引擎 路由功能
- KFC的世界里,库就是一个小套餐, 框架就是全家桶
- 代码上的不同
- 一般使用库的代码,是调用某个函数,我们自己把控库的代码
- 一般使用框架,其框架在帮我们运行我们编写好的代码
- 框架: 初始化自身的一些行为
- 执行你所编写的代码
- 施放一些资源
- 框架: 初始化自身的一些行为
vue起步
- 1:引包
- 2:启动
new Vue({
el:'#app',//目的地
data:{
//保存数据的地方
},
template:`模板内容`
});
插值表达式
- {{ 表达式 }}
- 对象 (不要连续3个{{ {name:'jack'} }})
- 字符串 {{ 'xxx' }}
- 判断后的布尔值 {{ true }}
- 三元表达式 {{ true?'是正确':'错误' }}
- 可以用于页面中简单粗暴的调试
- 要用插值表达式 必须要data中声明该属性
什么是指令
- 在vue中提供了一些对于页面 数据的更为方便的输出,这些操作就叫做指令, 以v-xxx表示
- 比如html页面中的属性
- 比如html页面中的属性
- 比如在angular中 以ng-xxx开头的就叫做指令
- 在vue中 以v-xxx开头的就叫做指令
- 指令中封装了一些DOM行为, 结合属性作为一个暗号, 暗号有对应的值,根据不同的值,框架会进行相关DOM操作的绑定
vue中常用的v-指令演示
- v-text:元素的textContent属性,必须是双标签 跟{{ }}效果是一样的 使用较少
- v-html: 元素的innerHTML
- v-if : 判断是否插入这个元素,相当于对元素的销毁和创建
- v-else-if
- v-else
- v-show 隐藏元素 如果确定要隐藏, 会给元素的style加上display:none。是基于css样式的切换
v-if和v-show的区别 (官网解释)
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
v-bind使用
- 给元素的属性赋值
- 可以给已经存在的属性赋值 input value
- 也可以给自定义属性赋值 mydata
- 语法 在元素上
v-bind:属性名="常量||变量名"
- 简写形式
:属性名="变量名"
- `html
#### v-on的使用
- 处理自定义原生事件的,给按钮添加click并让使用变量的样式改变
- 普通使用 ```v-on:事件名="表达式||函数名"```
- 简写方式 ``` @事件名="表达式"```
#### v-model 双向的数据绑定
- 双向数据流(绑定)
- 页面改变影响内存(js)
- 内存(js)改变影响页面
#### v-bind 和 v-model 的区别
- `input v-model="name"`
- 双向数据绑定 页面对于input的value改变,能影响内存中name变量
- 内存js改变name的值,会影响页面重新渲染最新值
- `input :value="name"`
- 单向数据绑定 内存改变影响页面改变
- v-model: 其的改变影响其他 v-bind: 其的改变不影响其他
- v-bind就是对属性的简单赋值,当内存中值改变,还是会触发重新渲染
#### v-for的使用
- 基本语法 `v-for="item in arr"`
- 对象的操作 `v-for="item in obj"`
- 如果是数组没有id
- `v-for="(item,index) in arr" :class="index" :key='index' `
- v-for的优先级最高
[TOC]
### 组件基础
#### 什么是组件
其实突然出来的这个名词,会让您不知所以然,如果大家使用过bootstrap的同学一定会对这个名词不陌生,我们其实在很早的时候就接触这个名词
通常一个应用会以一颗嵌套的组件树的形式来阻止:
![](https://img-blog.csdnimg.cn/20191210181341252.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTk5MjcxMA==,size_16,color_FFFFFF,t_70)
#### 局部组件
使用局部组件的打油诗: 建子 挂子 用子
> 注意:在组件中这个data必须是一个函数,返回一个对象
},
components: {
// 2.挂载子组件
App
}
})</script>
#### 全局组件
通过`Vue.component(组件名,{})`创建全局组件,此时该全局组件可以在任意模板(template)中使用
Vue.component('Child',{template:`
我是一个子组件
#### 组件通信
##### 父传子
如果一个网页有一个博文组件,但是如果你不能向这个组件传递某一篇博文的标题和内容之类想展示的数据的话,它是没有办法使用的.这也正是prop的由来
父组件往子组件通信:**通过Prop向子组件传递数据**
Vue.component('Child',{template:`
我是一个子组件
{{childData}}
}
}
```
- 在子组件中声明props接收在父组件挂载的属性
- 可以在子组件的template中任意使用
- 在父组件绑定自定义的属性
子传父
网页上有一些功能可能要求我们和父组件组件进行沟通
子组件往父组件通信: 监听子组件事件,使用事件抛出一个值
Vue.component('Child', {
template: `
<div>
<h3>我是一个子组件</h3>
<h4>{{childData}}</h4>
<input type="text" @input = 'handleInput'/>
</div>
`,
props: ['childData'],
methods:{
handleInput(e){
const val = e.target.value;
//使用$emit触发子组件的事件
this.$emit('inputHandler',val);
}
},
})
const App = {
data() {
return {
msg: '我是父组件传进来的值',
newVal:''
}
},
methods:{
input(newVal){
// console.log(newVal);
this.newVal = newVal;
}
},
template: `
<div>
<div class='father'>
数据:{{newVal}}
</div>
<!--子组件监听事件-->
<Child :childData = 'msg' @inputHandler = 'input'></Child>
</div>
`,
computed: {
}
}
- 在父组件中 子组件上绑定自定义事件
- 在子组件中 触发原生的事件 在事件函数通过this.$emit触发自定义的事件
平行组件
在开发中,可能会存在没有关系的组件通信,比如有个博客内容显示组件,还有一个表单提交组件,我们现在提交数据到博客内容组件显示,这显示有点费劲.
为了解决这种问题,在vue中我们可以使用bus,创建中央事件总线
const bus = new Vue();
// 中央事件总线 bus
Vue.component('B', {
data() {
return {
count: 0
}
},
template: `
<div>{{count}}</div>
`,
created(){
// $on 绑定事件
bus.$on('add',(n)=>{
this.count =n;
})
}
})
Vue.component('A', {
data() {
return {
}
},
template: `
<div>
<button @click='handleClick'>加入购物车</button>
</div>
`,
methods:{
handleClick(){
// 触发绑定的函数 // $emit 触发事件
bus.$emit('add',1);
}
}
})
其它组件通信方式
父组件 provide来提供变量,然后再子组件中通过inject来注入变量.无论组件嵌套多深
Vue.component('B', {
data() {
return {
count: 0
}
},
inject:['msg'],
created(){
console.log(this.msg);
},
template: `
<div>
{{msg}}
</div>
`,
})
Vue.component('A', {
data() {
return {
}
},
created(){
// console.log(this.$parent.$parent);
// console.log(this.$children);
console.log(this);
},
template: `
<div>
<B></B>
</div>
`
})
new Vue({
el: '#app',
data: {
},
components: {
// 2.挂载子组件
App
}
})
插槽
匿名插槽
子组件定义 slot 插槽,但并未具名,因此也可以说是默认插槽。只要在父元素中插入的内容,默认加入到这个插槽中去
Vue.component('MBtn', {
template: `
<button>
<slot></slot>
</button>
`,
props: {
type: {
type: String,
defaultValue: 'default'
}
},
})
const App = {
data() {
return {
}
},
template: `
<div>
<m-btn>登录</m-btn>
<m-btn>注册</m-btn>
<m-btn>提交</m-btn>
</div>
`,
}
new Vue({
el: '#app',
data: {
},
components: {
// 2.挂载子组件
App
}
})
具名插槽
具名插槽可以出现在不同的地方,不限制出现的次数。只要匹配了 name 那么这些内容就会被插入到这个 name 的插槽中去
Vue.component('MBtn',{
template:`
<button :class='type' @click='clickHandle'>
<slot name='register'></slot>
<slot name='login'></slot>
<slot name='submit'></slot>
</button>
`,
props:{
type:{
type: String,
defaultValue: 'default'
}
},
methods:{
clickHandle(){
this.$emit('click');
}
}
})
const App = {
data() {
return {
}
},
methods:{
handleClick(){
alert(1);
},
handleClick2(){
alert(2);
}
},
template: `
<div>
<MBtn type='default' @click='handleClick'>
<template slot='register'>
注册
</template>
</MBtn>
<MBtn type='success' @click='handleClick2'>
<template slot='login'>
登录
</template>
</MBtn>
<MBtn type='danger'>
<template slot='submit'>
提交
</template>
</MBtn>
</div>
`,
}
new Vue({
el: '#app',
data: {
},
components: {
App
}
})
作用域插槽
通常情况下普通的插槽是父组件使用插槽过程中传入东西决定了插槽的内容。但有时我们需要获取到子组件提供的一些数据,那么作用域插槽就排上用场了
Vue.component('MyComp', {
data(){
return {
data:{
username:'小马哥'
}
}
},
template: `
<div>
<slot :data = 'data'></slot>
<slot :data = 'data' name='one'></slot>
</div>
`
})
const App = {
data() {
return {
}
},
template: `
<div>
<MyComp>
<!--默认的插槽 default可以省略-->
<template v-slot:default='user'>
{{user.data.username}}
</template>
</MyComp>
<MyComp>
<!--与具名插槽配合使用-->
<template v-slot:one='user'>
{{user.data.username}}
</template>
</MyComp>
</div>
`,
}
new Vue({
el: '#app',
data: {
},
components: {
App
}
})
作用域插槽应用
先说一下我们假设的应用常用场景,我们已经开发了一个代办事项列表的组件,很多模块在用,现在要求在不影响已测试通过的模块功能和展示的情况下,给已完成的代办项增加一个对勾效果。
也就是说,代办事项列表组件要满足一下几点
- 之前数据格式和引用接口不变,正常展示
- 新的功能模块增加对勾
const todoList = {
data(){
return {
}
},
props:{
todos:Array,
defaultValue:[]
},
template:`
<ul>
<li v-for='item in todos' :key='item.id'>
<slot :itemValue='item'>
{{item.title}}
</slot>
</li>
</ul>
`
}
const App = {
data() {
return {
todoList: [
{
title: '大哥你好么',
isComplate:true,
id: 1
},
{
title: '小弟我还行',
isComplate:false,
id: 2
},
{
title: '你在干什么',
isComplate:false,
id: 3
},
{
title: '抽烟喝酒烫头',
isComplate:true,
id: 4
}
]
}
},
components:{
todoList
},
template: `
<todoList :todos='todoList'>
<template v-slot='data'>
<input type='checkbox' v-model='data.itemValue.isComplate'/>
{{data.itemValue.title}}
</template>
</todoList>
`,
}
new Vue({
el: '#app',
data: {
},
components: {
App
}
})
生命周期
“你不需要立马弄明白所有的东西,不过随着你的不断学习和使用,它的参考价值会越来越高。
当你在做项目过程中,遇到了这种问题的时候,再回过头来看这张图
什么是生命周期
每个 Vue 实例在被创建时都要经过一系列的初始化过程。例如:从开始创建、初始化数据、编译模板、挂载Dom、数据变化时更新DOM、卸载等一系列过程。我们称 这一系列的过程 就是Vue的生命周期。通俗说就是Vue实例从创建到销毁的过程,就是生命周期。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会,利用各个钩子来完成我们的业务代码。
干活满满
生命周期钩子
beforCreate
实例初始化之后、创建实例之前的执行的钩子事件
Vue.component('Test',{
data(){
return {
msg:'小马哥'
}
},
template:`
<div>
<h3>{{msg}}</h3>
</div>
`,
beforeCreate:function(){
// 组件创建之前
console.log(this.$data);//undefined
}
})
效果:
创建实例之前,数据观察和事件配置都没好准备好。也就是数据也没有、DOM也没生成
created
实例创建完成后执行的钩子
created() {
console.log('组件创建', this.$data);
}
效果:
实例创建完成后,我们能读取到数据data的值,但是DOM还没生成,可以在此时发起ajax
beforeMount
将编译完成的html挂载到对应的虚拟DOM时触发的钩子 此时页面并没有内容。 即此阶段解读为: 即将挂载
beforeMount(){
// 挂载数据到 DOM之前会调用
console.log('DOM挂载之前',document.getElementById('app'));
}
效果:
mounted
编译好的html挂载到页面完成后所执行的事件钩子函数
mounted() {
console.log('DOM挂载完成',document.getElementById('app'));
}
效果:
beforeUpdate和updated
beforeUpdate() {
// 在更新DOM之前 调用该钩子,应用:可以获取原始的DOM
console.log('DOM更新之前', document.getElementById('app').innerHTML);
},
updated() {
// 在更新DOM之后调用该钩子,应用:可以获取最新的DOM
console.log('DOM更新完成', document.getElementById('app').innerHTML);
}
效果:
beforeDestroy和destroyed
当子组件在v-if的条件切换时,该组价处于创建和销毁的状态
beforeDestroy() {
console.log('beforeDestroy');
},
destroyed() {
console.log('destroyed');
},
activated和deactivated
当配合vue的内置组件
一起使用的时候,才会调用下面此方法
组件的作用它可以缓存当前组件
activated() {
console.log('组件被激活了');
},
deactivated() {
console.log('组件被停用了');
},
组件进阶
动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里
const bus = new Vue();
Vue.component('TabLi', {
data() {
return {
}
},
methods: {
clickHandler(title) {
bus.$emit('handleChange', title);
}
},
props: ['tabTitles'],
template: `
<ul>
<li @click='clickHandler(title)' v-for='(title,i) in tabTitles' :key='i'>{{title}}</li>
</ul>
`
})
const Home = {
data() {
return {
isActive:false
}
},
methods: {
handleClick(){
this.isActive = true;
}
},
template: `<div @click='handleClick' :class='{active:isActive}'>Home Component</div>`
}
const Posts = {
data() {
return {
}
},
template: `<div>Posts Component</div>`
}
const Archive = {
data() {
return {
}
},
template: `<div>Archive Component</div>`
}
Vue.component('TabComp', {
data() {
return {
title: 'Home'
}
},
created() {
bus.$on('handleChange', (title) => {
this.title = title
})
},
template: `
<div class='content'>
<componet :is='title'></componet>
</div>
`,
components: {
Home,
Posts,
Archive
}
})
const App = {
data() {
return {
tabTitles: ['Home', 'Posts', 'Archive']
}
},
template: `
<div>
<TabLi :tabTitles='tabTitles'></TabLi>
<TabComp></TabComp>
</div>
`,
}
new Vue({
el: '#app',
data() {
return {
}
},
components: {
App,
}
})
使用is
特性来切换不同的组件
当在这些组件之间切换的时候,有时候会想保持这些组件的状态,以避免反复渲染导致的性能问题
在动态组件上使用keep-alive
<keep-alive>
<componet :is='title'></componet>
</keep-alive>
异步组件
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:
const App = {
data() {
return {
isShow:false
}
},
methods:{
asyncLoadTest(){
this.isShow = true;
}
},
template:`
<div>
<button @click='asyncLoadTest'>异步加载</button>
<test v-if='isShow'/>
</div>
`,
components:{
//异步加载组件
test:()=>import('./Test.js')
}
}
new Vue({
el:'#app',
data(){
return {
}
},
components:{
App
}
})
效果显示:
获取DOM和子组件对象
尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref
特性为这个子组件赋予一个 ID 引用。例如:
const Test = {
template: `<div class='test'>我是测试组件</div>`
}
const App = {
data() {
return {
}
},
created() {
console.log(this.$refs.test); //undefined
},
mounted() {
// 如果是组件挂载了ref 获取是组件对象,如果是标签挂载了ref,则获取的是DOM元素
console.log(this.$refs.test);
console.log(this.$refs.btn);
// 加载页面 让input自动获取焦点
this.$refs.input.focus();
},
components: {
Test
},
template: `
<div>
<button ref = 'btn'></button>
<input type="text" ref='input'>
<Test ref = 'test'></Test>
</div>
`
}
new Vue({
el: '#app',
data: {
},
components: {
App
}
})
nextTick的用法
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新
有些事情你可能想不到,vue在更新DOM时是异步执行的.只要侦听到数据变化,Vue将开启一个队列,并缓存在同一事件循环中发生的所有数据变更.如果同一个wather被多次触发,只会被推入到队列中一次.这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作
<div id="app">
<h3>{{message}}</h3>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el:'#app',
data:{
message:'123'
}
})
vm.message = 'new Message';//更新数据
console.log(vm.$el.textContent); //123
Vue.nextTick(()=>{
console.log(vm.$el.textContent); //new Message
})
</script>
当你设置vm.message = 'new Message'
,该组件不会立即重新渲染.当刷新队列时,组件会在下一个事件循环'tick'中更新.多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数将在 DOM 更新完成后被调用。
nextTick的应用
有个需求:
在页面拉取一个接口,这个接口返回一些数据,这些数据是这个页面的一个浮层组件要依赖的,然后我在接口一返回数据就展示了这个浮层组件,展示的同时,上报一些数据给后台(这些数据就是父组件从接口拿的),这个时候,神奇的事情发生了,虽然我拿到数据了,但是浮层展现的时候,这些数据还未更新到组件上去,上报失败
const Pop = {
data() {
return {
isShow:false
}
},
template:`
<div v-show = 'isShow'>
{{name}}
</div>
`,
props:['name'],
methods: {
show(){
this.isShow = true;
alert(this.name);
}
},
}
const App = {
data() {
return {
name:''
}
},
created() {
// 模拟异步请求的数据
setTimeout(() => {
this.name = '小马哥',
this.$refs.pop.show();
}, 2000);
},
components:{
Pop
},
template: `<pop ref='pop' :name='name'></pop>`
}
const vm = new Vue({
el: '#app',
components: {
App
}
})
完美解决:
created() {
// 模拟异步请求的数据
setTimeout(() => {
this.name = '小马哥',
this.$nextTick(()=>{
this.$refs.pop.show();
})
}, 2000);
},
对象变更检测注意事项
由于JavaScript的限制,Vue不能检测对象属性的添加和删除
对于已经创建的实例,Vue不允许动态添加根级别的响应式属性.但是,可以通过Vue.set(object,key,value)
方法向嵌套独享添加响应式属性
<div id="app">
<h3>
{{user.name}}{{user.age}}
<button @click='handleAdd'>添加年龄</button>
</h3>
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el:'#app',
data:{
user:{},
},
created() {
setTimeout(() => {
this.user = {
name:'张三'
}
}, 1250);
},
methods: {
handleAdd(){
console.log(this);
// 无响应式
// this.user.age = 20;
// 响应式的
this.$set(this.user,'age',20);
}
},
})
</script>
this.$set(this.user,'age',20);//它只是全局Vue.set的别名
如果想为已存在的对象赋值多个属性,可以使用Object.assign()
// 一次性响应式的添加多个属性
this.user = Object.assign({}, this.user, {
age: 20,
phone: '113131313'
})
混入mixin偷懒
混入(mixin)提供了一种非常灵活的方式,来分发Vue组件中的可复用功能.一个混入对象可以包含任意组件选项.
一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
<div id="app">
{{msg}}
</div>
<script src="./vue.js"></script>
<script>
const myMixin = {
data(){
return {
msg:'123'
}
},
created() {
this.sayHello()
},
methods: {
sayHello(){
console.log('hello mixin')
}
},
}
new Vue({
el: '#app',
data(){
return {
msg:'小马哥'
}
},
mixins: [myMixin]
})
mixin应用
有一种很难常见的情况:有两个非常相似的组件,他们共享同样的基本函数,并且他们之间也有足够的不同,这时你站在了一个十字路口:我是把它拆分成两个不同的组件?还是只使用一个组件,创建足够的属性来改变不同的情况。
这些解决方案都不够完美:如果你拆分成两个组件,你就不得不冒着如果功能变动你要在两个文件中更新它的风险,这违背了 DRY 前提。另一方面,太多的属性会很快会变得混乱不堪,对维护者很不友好,甚至是你自己,为了使用它,需要理解一大段上下文,这会让你感到失望。
使用混合。Vue 中的混合对编写函数式风格的代码很有用,因为函数式编程就是通过减少移动的部分让代码更好理解。混合允许你封装一块在应用的其他组件中都可以使用的函数。如果被正确的使用,他们不会改变函数作用域外部的任何东西,所以多次执行,只要是同样的输入你总是能得到一样的值。这真的很强大。
我们有一对不同的组件,他们的作用是切换一个状态布尔值,一个模态框和一个提示框.这些提示框和模态框除了在功能,没有其它共同点:它们看起来不一样,用法不一样,但是逻辑一样
<div id="app">
<App></App>
</div>
<script src="./vue.js"></script>
<script>
// 全局混入 要格外小心 每次实例创建 都会调用
Vue.mixin({
created(){
console.log('hello from mixin!!');
}
})
// 抽离
const toggleShow = {
data() {
return {
isShow: false
}
},
methods: {
toggleShow() {
this.isShow = !this.isShow
}
}
}
const Modal = {
template: `<div v-if='isShow'><h3>模态框组件</h3></div>`,
data() {
return {
}
},
mixins:[toggleShow]
}
const ToolTip = {
data() {
return {
}
},
template: `<div v-if='isShow'><h3>提示组件</h3></div>`,
mixins:[toggleShow]
}
const App = {
data() {
return {
}
},
template: `
<div>
<button @click='handleModel'>模态框</button>
<button @click='handleToolTip'>提示框</button>
<Modal ref='modal'></Modal>
<ToolTip ref="toolTip"></ToolTip>
</div>
`,
components: {
Modal,
ToolTip
},
methods: {
handleModel() {
this.$refs.modal.toggleShow()
},
handleToolTip() {
this.$refs.toolTip.toggleShow()
}
},
}
new Vue({
el: '#app',
data: {},
components: {
App
},
})
最后
还有2件事拜托大家
一:求赞 求收藏 求分享 求留言,让更多的人看到这篇内容
二:欢迎添加我的个人微信
备注“资料”, 300多篇原创技术文章,海量的视频资料即可获得
备注“加群”,我会拉你进技术交流群,群里大牛学霸具在,哪怕您做个潜水鱼也会学到很多东西