Vue学习总结之Vue的模板语法、计算属性、指令(四)

寄语:自从厌倦于追寻,我已学会一觅其中。自从一股逆风袭来,我已能抵御八面来风,驾舟而行。javascript

本文已收录至github.com/likekk/stud…欢迎你们star,共同窗习,共同进步。若是文章有错误的地方,欢迎你们指出。后期将在将GitHub上规划前端学习的路线和资源分享。html

写在前面

每一篇文章都但愿您有所收获,每一篇文章都但愿您能静下心来浏览、阅读。每一篇文章都是做者精心打磨的做品。前端

若是您以为杨戬这个前端小白还有点东西的话,做者但愿你能够帮忙点亮那个点赞的按钮,对于二郎神杨戬这个暖男来讲,真的真的很是重要,这将是我持续写做的动力。vue

前言

上一篇文章的内容主要是关于vue-cli项目的搭建、vue-cli项目引入第三方插件的相关内容,插图比较多,这也多是搭建类博客的特色,固然之后写做尽可能少用插图的,我尽可能将本身理解的意思表达出来。本篇博客的内容比较简单。内容包括Vue模板、computed计算属性、watch侦听属性、methods方法、事件处理等相关内容。java

Vue模板

Vue.js 使用了基于 HTML 的模板语法,容许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。全部 Vue.js 的模板都是合法的 HTML,因此能被遵循规范的浏览器和 HTML 解析器解析。ios

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 可以智能地计算出最少须要从新渲染多少组件,并把 DOM 操做次数减到最少。git

插值

一、文本

文本渲染的话主要是使用{{}}进行渲染,固然这也是数据的渲染方式程序员

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <ul>  <li v-for="(item,index) of fruit">{{item}}</li>  </ul> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  fruit:["苹果","香蕉","雪梨"]  }  }) </script> </body> </html>  复制代码

经过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。github

注意:使用v-once的时候,只会在数据渲染的时候第一次生效,改变它的值,那么不会从新渲染web

二、原始Html

有时候咱们可能有这样或那样的需求,输出真正的html代码,Vue也为咱们提供了一个属性v-html

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <h2 v-html="joinHtml"></h2> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{   },  computed:{  joinHtml(){  return "<span>我是经过v-html进行渲染的</span>"  }  }  }) </script> </body> </html>  复制代码

注意:站点上动态渲染任意 HTML 可能会很是危险,由于它很容易致使XSS 攻击。毫不要对用户提供的内容使用插值。

三、绑定属性

属性的话通常是以v-bind:[属性]的格式进行绑定的,例如咱们须要绑定图片的src属性,v-bind:src,绑定其它标签的title属性,v-bind:title等等。

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <a v-bind:href="href" v-bind:title="title" :data-id="id">百度一下</a> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  href:"https://www.baidu.com",  title:"点击连接,跳转百度",  id:"10001"  }  }) </script> </body> </html>  复制代码

若是一个标签上绑定多个属性,那么咱们就须要写一大堆的v-bind:, 这样的话代码的冗余度就会很高,因此咱们也能够直接使用:[属性] 进行绑定。

四、使用JavaScript表达式

文本插值中不只能够直接使用数据,还能够对数据进行处理

{{ number + 1 }}
 {{ ok ? 'YES' : 'NO' }}  {{ message.split('').reverse().join('') }}  <div v-bind:id="'list-' + id"></div> 复制代码

这些表达式会在所属 Vue 实例的数据做用域下做为 JavaScript 被解析。有个限制就是,每一个绑定都只能包含单个表达式,因此下面的例子都不会生效。

<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}  <!-- 流控制也不会生效,请使用三元表达式 --> {{ if (ok) { return message } }} 复制代码

固然还可使用方法、计算属性、过滤器等等。

指令

指令是带有 v- 前缀的特殊属性。指令属性的值预期是单个 JavaScript 表达式 ,指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地做用于 DOM。

就拿v-if来讲

<p v-if="seen">如今你看到我了</p>
复制代码

这里的p标签显示或者隐藏取决于seen的true或false。

一、参数

一些指令可以接收一个“参数”,在指令名称以后以冒号表示。例如,v-bind(:) 指令能够用于响应式地更新 HTML 属性

<a v-bind:href="url">...</a>
复制代码

在这里 href 是参数,告知 v-bind 指令将该元素的 href属性与表达式 url 的值绑定。

另外一个例子是 v-on(@click)指令,它用于监听 DOM 事件:

<a v-on:click="alertMsg">弹出消息</a>
复制代码

alertMsg表示的是方法名(事件名)

二、动态参数

能够用方括号括起来的 JavaScript 表达式做为一个指令的参数:

<a v-bind:[attributeName]="url"> ... </a>
复制代码

这里的 attributeName 会被做为一个 JavaScript 表达式进行动态求值,求得的值将会做为最终的参数来使用。例如,若是你的 Vue 实例有一个 data property attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href

三、修饰符

修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault()

<form v-on:submit.prevent="onSubmit">...</form>
复制代码
  • .stop - 调用 event.stopPropagation()。
  • .prevent - 调用 event.preventDefault()。
  • .capture - 添加事件侦听器时使用 capture 模式。
  • .self - 只当事件是从侦听器绑定的元素自己触发时才触发回调。
  • . keyCode keyAlias - 只当事件是从特定键触发时才触发回调。
  • .native - 监听组件根元素的原生事件。
  • .once - 只触发一次回调。
  • .left - 只当点击鼠标左键时触发。
  • .right - 只当点击鼠标右键时触发。
  • .middle - 只当点击鼠标中键时触发。
  • .passive - 以 { passive: true } 模式添加侦听器

缩写

关于缩写上文已经说起到,减小代码的冗余。

一、v-bind的缩写

<!-- 完整语法 -->
<a v-bind:href="url">...</a>  <!-- 缩写 --> <a :href="url">...</a>  <!-- 动态参数的缩写 (2.6.0+) --> <a :[key]="url"> ... </a> 复制代码

二、v-on的缩写

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>  <!-- 缩写 --> <a @click="doSomething">...</a>  <!-- 动态参数的缩写 (2.6.0+) --> <a @[event]="doSomething"> ... </a> 复制代码

computed计算属性

计算属性也是咱们在Vue中常常会使用到的一个特性,和下文的methods、watch有着类似之处和非类似之处。

那咱们一块儿来看看,

引子

有这样一个需求,假设须要在花括号中反转字符串,那么咱们的第一反应就是这样写

<span>{{message.spilt('').reverse.join('')}}</span> 复制代码

那么问题来了,这是一个标签这样写,我以为还很OK,可是若是有一百个,一千个呢?将这样的代码写在界面上,那么确定维护起来十分不方便。因此咱们须要将这些的逻辑处理进行拆分。

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <p>原始数据{{message}}</p>  <p>反转后的数据{{reverseMessage}}</p> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  message:"hello"  },  computed:{  reverseMessage(){  return this.message.split('').reverse().join('');  }  }  }) </script> </body> </html>  复制代码

computed计算属性须要注意的事项:

  • computed中定义的方法只容许当属性用,不能带参数,这限制它的复用性
  • 当方法中的属性发生变化时方法将从新调用
  • 不该该使用箭头函数来定义计算属性里面的函数
  • computed计算属性能够对属性进行缓存,计算属性只有当该属性发生变化时才会从新计算值
  • 若是一个属性不能完成须要的功能时能够考虑转换为计算属性

methods方法

方法也同时能够实现上述的效果,看下方法是如何实现的

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <p>原始数据{{message}}</p>  <p>反转后的数据{{reverseMessage}}</p>  <p>调用方法反转后的数据{{reverseMessageOfMethods()}}</p> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  message:"hello"  },  computed:{  reverseMessage(){  return this.message.split('').reverse().join('');  }  },  methods:{  reverseMessageOfMethods(){  return this.message.split('').reverse().join('');  }  }  }) </script> </body> </html>  复制代码

咱们能够将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是彻底相同的。然而,不一样的是计算属性是基于它们的响应式依赖进行缓存的

只在相关响应式依赖发生改变时它们才会从新求值。这就意味着只要 message 尚未发生改变,屡次访问 reversedMessage计算属性会当即返回以前的计算结果,而没必要再次执行函数。

methods方法应该须要注意的事项

methods里面定义的方法不该该使用箭头函数,缘由是箭头函数绑定了父级做用域上下文,因此this不是Vue实例

咱们为何须要缓存?假设咱们有一个性能开销比较大的计算属性 A,它须要遍历一个巨大的数组并作大量的计算。而后咱们可能有其余的计算属性依赖于 A。若是没有缓存,咱们将不可避免的屡次执行 A 的 getter!若是你不但愿有缓存,请用方法来替代。

计算属性VS监听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变更:侦听属性。当你有一些数据须要随着其它数据变更而变更时,你很容易滥用 watch——特别是若是你以前使用过 AngularJS。然而,一般更好的作法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="demo">  <span>{{ fullName }}</span> </div> <script src="../js/vue.js"></script> <script>  var vm = new Vue({  el: '#demo',  data: {  firstName: 'Foo',  lastName: 'Bar',  fullName: 'Foo Bar'  },  watch: {  firstName: function (val) {  this.fullName = val + ' ' + this.lastName  },  lastName: function (val) {  this.fullName = this.firstName + ' ' + val  }  }  }) </script> </body> </html>  复制代码

上面代码是命令式且重复的。将它与计算属性的版本进行比较:

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="demo">  <span>{{ fullName }}</span> </div> <script src="../js/vue.js"></script> <script>  var vm = new Vue({  el: '#demo',  data: {  firstName: 'Foo',  lastName: 'Bar'  },  computed: {  fullName: function () {  return this.firstName + ' ' + this.lastName  }  }  }) </script> </body> </html>  复制代码

计算属性Setter

计算属性默认只有 getter,不过在须要时你也能够提供一个 setter:

computed: {
 fullName: {  // getter  get: function () {  return this.firstName + ' ' + this.lastName  },  // setter  set: function (newValue) {  var names = newValue.split(' ')  this.firstName = names[0]  this.lastName = names[names.length - 1]  }  } } 复制代码

watch监听属性

虽然计算属性在大多数状况下更合适,但有时也须要一个自定义的侦听器。这就是为何 Vue 经过 watch 选项提供了一个更通用的方法,来响应数据的变化。当须要在数据变化时执行异步或开销较大的操做时,这个方式是最有用的。

<div id="watch-example">
 <p>  Ask a yes/no question:  <input v-model="question">  </p>  <p>{{ answer }}</p> </div> 复制代码
<!-- 由于 AJAX 库和通用工具的生态已经至关丰富,Vue 核心代码没有重复 -->
<!-- 提供这些功能以保持精简。这也可让你自由选择本身更熟悉的工具。 --> <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script> <script> var watchExampleVM = new Vue({  el: '#watch-example',  data: {  question: '',  answer: 'I cannot give you an answer until you ask a question!'  },  watch: {  // 若是 `question` 发生改变,这个函数就会运行  question: function (newQuestion, oldQuestion) {  this.answer = 'Waiting for you to stop typing...'  this.debouncedGetAnswer()  }  },  created: function () {  // `_.debounce` 是一个经过 Lodash 限制操做频率的函数。  // 在这个例子中,咱们但愿限制访问 yesno.wtf/api 的频率  // AJAX 请求直到用户输入完毕才会发出。想要了解更多关于  // `_.debounce` 函数 (及其近亲 `_.throttle`) 的知识,  // 请参考:https://lodash.com/docs#debounce  this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)  },  methods: {  getAnswer: function () {  if (this.question.indexOf('?') === -1) {  this.answer = 'Questions usually contain a question mark. ;-)'  return  }  this.answer = 'Thinking...'  var vm = this  axios.get('https://yesno.wtf/api')  .then(function (response) {  vm.answer = _.capitalize(response.data.answer)  })  .catch(function (error) {  vm.answer = 'Error! Could not reach the API. ' + error  })  }  } }) </script> 复制代码

三者的区别:

  • 计算属性computed:计算属性只能看成属性用,不能带参数,有缓存,效率高,,能够直接与v-model绑定
  • 方法methods:方法能够直接调用,可带参数,没有缓存,每次调用都会执行,效率不如计算属性高,不可与v-model绑定
  • 监视watch:不能显式调用(被监视的对象变化时被动调用),能够对变化的控制更加具体,但应用复杂,能够间接与v-model绑定

过滤器(Filters)

过滤器也是Vue中一个很是强大的功能,可被用于一些常见的文本格式化,过滤器用在两个地方双花括号插值和v-bind表达式,过滤器应该添加被添加在JavaScript表达式的尾部,由“管道”符号指示:

<!-- 在双花括号中 -->
{{ message | capitalize }}  <!-- 在 `v-bind` 中 --> <div v-bind:id="rawId | formatId"></div> 复制代码

你能够在一个组件的选项中定义本地的过滤器:

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <span> {{message|capitalize}}</span> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  message:"hello"  },  methods:{   },  filters:{  capitalize(value){  if(!value){  return ""  }  value=value.toString();  return value.charAt(0).toUpperCase()+value.slice(1);  }  }  }) </script> </body> </html>  复制代码

或者在建立 Vue 实例以前全局定义过滤器:

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <span> {{message|capitalize}}</span> </div> <script src="../js/vue.js"></script> <script>  Vue.filter("capitalize",function (value) {  if(!value){  return ""  }  value=value.toString();  return value.charAt(0).toUpperCase()+value.slice(1);  })  const vm=new Vue({  el:'#app',  data:{  message:"hello"  },  methods:{   },  }) </script> </body> </html>  复制代码

当全局过滤器和局部过滤器重名时,会采用局部过滤器

过滤器函数总接收表达式的值 (以前的操做链的结果) 做为第一个参数。在上述例子中,capitalize 过滤器函数将会收到 message 的值做为第一个参数。

过滤器能够串联:

{{ message | filterA | filterB }}
复制代码

在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 message 的值将做为参数传入到函数中。而后继续调用一样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。

过滤器是 JavaScript 函数,所以能够接收参数:

{{ message | filterA('arg1', arg2) }}
复制代码

这里,filterA 被定义为接收三个参数的过滤器函数。其中 message 的值做为第一个参数,普通字符串 'arg1' 做为第二个参数,表达式 arg2 的值做为第三个参数。

Vue经常使用的指令和事件处理器

上文中咱们说起到了指令和事件,那么如今就好好的聊聊。

经常使用的指令

v-text
v-html v-show v-if v-else v-eles-if v-for v-on v-bind v-model v-slot v-pre v-cloak v-once  复制代码

事件处理器

一般咱们都是使用v-on:[事件名]来触发事件的,例如单击事件v-on:click等

一、监听事件
<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <button @click="count+=1">+</button>  <span>{{count}}</span> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  count:1  }  }) </script> </body> </html> 复制代码

二、事件处理方法

然而许多事件处理逻辑会更为复杂,因此直接把 JavaScript 代码写在 v-on 指令中是不可行的(如上示例,点击按钮的时候实现数字加1)。所以 v-on 还能够接收一个须要调用的方法名称。将方法注册在methods中

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <button @click="greet">问候</button>  <button @click="sum($event)">计算</button> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  count:1  },  methods:{  greet(event){  console.log(this.count);  if(event){  console.log(event.currentTarget);  }  },  sum(event){  console.log(event.currentTarget);  }   }  }) </script> </body> </html>  复制代码

这里有一点须要注意就是关于事件添加括号和没有括号的状况

当事件没有括号的时候默认传入的第一个参数是event

当事件有括号的时候,若是想要获取event这个对象,咱们须要手动传入$event这个属性

三、内联处理器中的方法

除了直接绑定到一个方法,也能够在内联 JavaScript 语句中调用方法:

<!DOCTYPE html>
<html lang="en"> <head>  <meta charset="UTF-8">  <title>Title</title> </head> <body> <div id="app">  <button @click="say('hi')">sayHi</button>  <button @click="say('hello')">sayHello</button> </div> <script src="../js/vue.js"></script> <script>  const vm=new Vue({  el:'#app',  data:{  count:1  },  methods:{  say(param){  console.log(param)  },  }  }) </script> </body> </html>  复制代码

这种是将方法进行复用,根据传入的参数不一样。

四、事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是很是常见的需求。

但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符。以前提过,修饰符是由点开头的指令后缀来表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>  <!-- 提交事件再也不重载页面 --> <form v-on:submit.prevent="onSubmit"></form>  <!-- 修饰符能够串联 --> <a v-on:click.stop.prevent="doThat"></a>  <!-- 只有修饰符 --> <form v-on:submit.prevent></form>  <!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即内部元素触发的事件先在此处理,而后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div>  <!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div>  <!-- 点击事件将只会触发一次 --> <a v-on:click.once="doThis"></a> 复制代码

注意:

使用修饰符时,顺序很重要;相应的代码会以一样的顺序产生。所以,用 v-on:click.prevent.self 会阻止全部的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

Vue 还对应 addEventListener中的 passive 选项提供了 .passive 修饰符。

<!-- 滚动事件的默认行为 (即滚动行为) 将会当即触发 -->
<!-- 而不会等待 `onScroll` 完成 --> <!-- 这其中包含 `event.preventDefault()` 的状况 --> <div v-on:scroll.passive="onScroll">...</div> 复制代码

这个 .passive 修饰符尤为可以提高移动端的性能。

注意:

不要把 .passive.prevent 一块儿使用,由于 .prevent 将会被忽略,同时浏览器可能会向你展现一个警告。请记住,.passive 会告诉浏览器你想阻止事件的默认行为。

五、按键修饰符

在监听键盘事件时,咱们常常须要检查详细的按键。Vue 容许为 v-on 在监听键盘事件时添加按键修饰符:

<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit"> 复制代码

你能够直接将 KeyboardEvent.key暴露的任意有效按键名转换为 kebab-case 来做为修饰符。

<input v-on:keyup.page-down="onPageDown">
复制代码

在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。

按键码

keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持。

使用 keyCode attribute 也是容许的:

<input v-on:keyup.13="submit">
复制代码

为了在必要的状况下支持旧浏览器,Vue 提供了绝大多数经常使用的按键码的别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

有一些按键 (.esc 以及全部的方向键) 在 IE9 中有不一样的 key 值, 若是你想支持 IE9,这些内置的别名应该是首选。

六、系统修饰符

能够用以下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器

  • .ctrl
  • .alt
  • .shift
  • .meta
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">  <!-- Ctrl + Click --> <div v-on:click.ctrl="doSomething">Do something</div> 复制代码

请注意修饰键与常规按键不一样,在和 keyup 事件一块儿用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的状况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。若是你想要这样的行为,请为 ctrl 换用 keyCodekeyup.17

一、.exact修饰符

.exact修饰符容许你控制由精确的系统修饰符组合触发的事件

<!-- 即便 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>  <!-- 有且只有 Ctrl 被按下的时候才触发 --> <button v-on:click.ctrl.exact="onCtrlClick">A</button>  <!-- 没有任何系统修饰符被按下的时候才触发 --> <button v-on:click.exact="onClick">A</button> 复制代码

二、鼠标按钮修饰符

  • left
  • right
  • middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮

结尾

若是以为本篇文章对您有用的话,能够麻烦您给笔者点亮那个点赞按钮。

对于杨戬这个暖男来讲:真的真的很是有用,您的支持将是我继续写文章前进的动力,咱们下篇文章见。

【原创】|二郎神杨戬

二郎神杨戬,一个在互联网前端苟且偷生的划水程序员,专一于前端开发,善于技术分享。 如需转载,请联系做者或者保留原文连接,微信公众号搜索二郎神杨戬或者扫描下方的二维码更加方便。

一块儿来见证二郎神杨戬的成长吧!更多好文、技术分享尽在下方这个公众号。欢迎关注。

相关文章
相关标签/搜索