Vue 基本知识

<!DOCTYPE html>

<html lang="CN">

<head>

  <meta charset="utf-8" />

  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

  <title>vue3快速上手</title>

  <meta name="viewport" content="width=device-width, initial-scale=1.0" />

  <style id="mkstylesheet">

body{-webkit-font-smoothing:antialiased;font-family:"Helvetica Neue",Helvetica,Arial,Verdana,sans-serif;margin:30px 0 0;padding:0;background:#fff;color:#303030;font-size:14px;line-height:1.45}#wrapper{padding:20px}li{font-size:110%}li li{font-size:100%}li p{font-size:100%;margin:.5em 0}h1{color:#000}h2{color:#111}h3{color:#111}h4{color:#111}h5{color:#111}h6{color:#111;font-style:italic}p,td,div{color:#111;font-family:"Helvetica Neue",Helvetica,Arial,Verdana,sans-serif;word-wrap:break-word}a{color:#0d6ea1;text-decoration:none;-webkit-transition:color .2s ease-in-out;-moz-transition:color .2s ease-in-out;-o-transition:color .2s ease-in-out;-ms-transition:color .2s ease-in-out;transition:color .2s ease-in-out}a:hover{color:#3593d9}h1,h2,h3,h4,h5{margin:2.75rem 0 1rem;font-weight:600;line-height:1.15}h1{margin-top:0;font-size:3.052em}h2{font-size:2.441em}h3{font-size:1.953em}h4{font-size:1.563em}h5{font-size:1.25em}ul,ol,pre,table,blockquote{margin-top:.625em;margin-bottom:.625em}hr{border:1px solid;margin:-1px 0}ul ul,ol ol,ul ol,ol ul{margin-top:0;margin-bottom:0}b,strong,em,small,code{line-height:1}.footnote{color:#0d6ea1;font-size:.8em;vertical-align:super}abbr,acronym{border-bottom:1px dotted #aaa}#wrapper img{max-width:100%;height:auto}dd{font-size:1em;margin-bottom:1em}li>p:first-of-type{margin:0}li p+p{margin-top:16px}ul,ol{padding-left:2.5rem}#wrapper ol{list-style-position:outside}ul ul,ul ol{margin-bottom:.4em}caption,col,colgroup,table,tbody,td,tfoot,th,thead,tr{border-spacing:0}table{border:1px solid rgba(0,0,0,0.25);border-collapse:collapse;display:table;empty-cells:hide;margin:-1px 0 1.3125em;padding:0;table-layout:fixed}caption{display:table-caption;font-weight:700}col{display:table-column}colgroup{display:table-column-group}tbody{display:table-row-group}tfoot{display:table-footer-group}thead{display:table-header-group}td,th{display:table-cell}tr{display:table-row}table th,table td{font-size:1.1em;line-height:1.3;padding:.5em 1em 0}table thead{background:rgba(0,0,0,0.15);border:1px solid rgba(0,0,0,0.15);border-bottom:1px solid rgba(0,0,0,0.2)}table tbody{background:rgba(0,0,0,0.05)}table tfoot{background:rgba(0,0,0,0.15);border:1px solid rgba(0,0,0,0.15);border-top:1px solid rgba(0,0,0,0.2)}figure{display:inline-block;overflow:hidden;position:relative;margin:1em 0 2em}figcaption{font-style:italic;text-align:center;background:white;color:#666}.poetry pre{display:block;font-family:Georgia,Garamond,serif !important;font-size:110% !important;font-style:italic;line-height:1.6em;margin-left:1em}.poetry pre code{font-family:Georgia,Garamond,serif !important;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto;white-space:pre-wrap}blockquote p{font-size:110%;font-style:italic;line-height:1.6em}sup,sub,a.footnote{font-size:1.4ex;height:0;line-height:1;position:relative;vertical-align:super}sub{vertical-align:sub;top:-1px}p{font-size:1.1429em;line-height:1.3125em;margin:1.3125em 0}dt,th{font-weight:700}table tr:nth-child(odd),table th:nth-child(odd),table td:nth-child(odd){background:rgba(255,255,255,0.06)}table tr:nth-child(even),table td:nth-child(even){background:rgba(200,200,200,0.25)}@media print{img,table,figure{page-break-inside:avoid}#wrapper{background:#fff;color:#303030;padding:10px;position:relative;text-indent:0}}@media screen{.inverted{background:#252a2a}.inverted #wrapper{background:#252a2a;color:#eee}.inverted hr{border-color:#333f40 !important}.inverted p,.inverted td,.inverted li,.inverted h1,.inverted h2,.inverted h3,.inverted h4,.inverted h5,.inverted h6,.inverted th,.inverted .math,.inverted caption,.inverted dt,.inverted dd{color:#eee}.inverted pre{background:#ccc;color:#111}.inverted table{background:none}.inverted table tr:nth-child(odd),.inverted table td:nth-child(odd){background:none}.inverted a{color:#acd1d5}::selection{background:rgba(157,193,200,0.5)}h1::selection{background-color:rgba(45,156,208,0.3)}h2::selection{background-color:rgba(90,182,224,0.3)}h3::selection,h4::selection,h5::selection,h6::selection,li::selection,ol::selection{background-color:rgba(133,201,232,0.3)}code::selection{background-color:rgba(0,0,0,0.7);color:#eee}code span::selection{background-color:rgba(0,0,0,0.7) !important;color:#eee !important}a::selection{background-color:rgba(255,230,102,0.2)}.inverted a::selection{background-color:rgba(255,230,102,0.6)}td::selection,th::selection,caption::selection{background-color:rgba(180,237,95,0.5)}}.mkstyle--swiss #wrapper aside.blurb:before{margin-top:.7em}

#mkreplaced-toc{list-style-position:inside;padding:0;margin:0 0 0 1rem;list-style-type:none}#mkreplaced-toc li::before{content:''}#mkreplaced-toc li{font-size:1rem;line-height:1.25;font-weight:normal}#mkreplaced-toc li ul{font-size:1.3rem;font-weight:300;padding:.5rem 0;margin:0 0 0 1rem}#mkreplaced-toc li.missing{list-style-type:none !important}#mkreplaced-toc.max-1 ul,#mkreplaced-toc.max1 ul{display:none}#mkreplaced-toc.max-2 ul ul,#mkreplaced-toc.max2 ul ul{display:none}#mkreplaced-toc.max-3 ul ul ul,#mkreplaced-toc.max3 ul ul ul{display:none}#mkreplaced-toc.max-4 ul ul ul ul,#mkreplaced-toc.max4 ul ul ul ul{display:none}#mkreplaced-toc.max-5 ul ul ul ul ul,#mkreplaced-toc.max5 ul ul ul ul ul{display:none}.mk-rtl{direction:rtl;text-align:right}body.mkkatex-number-equations{counter-reset:eqnum}body.mkkatex-number-equations .katex-display{position:relative}body.mkkatex-number-equations .katex-display::after{counter-increment:eqnum;content:"(" counter(eqnum) ")";position:absolute;left:0;top:25%}body.mkkatex-number-equations.mkkatex-number-equations-right .katex-display::after{right:0;left:auto}.mkprinting,.mkprinting #wrapper{height:auto;margin-bottom:0;padding-bottom:0}.hideProgress #generated-toc,.hideProgress #firstdiff,.hideProgress #toc-title,.hideProgress #mkdocumentprogress,.hideProgress #mkincludechart,.hideProgress #mkprogressbar1,.hideProgress #mkprogressbar2,.hideProgress b.bookmark,.hideProgress .mkscrollmeter,.hideProgress #alllinks,.hideProgress #criticnav,.hideProgress .popup,.hideProgress #progressindicator,.hideProgress #mkautoscroll,.mkprinting #generated-toc,.mkprinting #firstdiff,.mkprinting #toc-title,.mkprinting #mkdocumentprogress,.mkprinting #mkincludechart,.mkprinting #mkprogressbar1,.mkprinting #mkprogressbar2,.mkprinting b.bookmark,.mkprinting .mkscrollmeter,.mkprinting #alllinks,.mkprinting #criticnav,.mkprinting .popup,.mkprinting #progressindicator,.mkprinting #mkautoscroll{display:none !important}.hideProgress .mkstyledtag,.mkprinting .mkstyledtag{display:none}.mkcolor-grammar-error,.mkcolor-spell-error{background:none;border-bottom:none}.mkprinting.mkshowcomments .mkstyledtag{display:inline;background:#ccc;padding:3px 9px;border-radius:20px;font-size:1}@media print{body{background:white;line-height:1.4}html,body,#wrapper{-moz-box-shadow:none;-webkit-box-shadow:none;box-shadow:none;-webkit-perspective:none !important;-webkit-text-size-adjust:none;border:0;box-sizing:border-box;float:none;margin:0;max-width:100%;padding:0;margin-top:0;width:auto}.critic #wrapper mark.crit{background-color:#fffd38 !important;text-decoration:none;color:#000}h1,h2,h3,h4,h5,h6{page-break-after:avoid}p,h2,h3{orphans:3;widows:3}section{page-break-before:avoid}pre>code{white-space:pre;word-break:break-word}#generated-toc,#firstdiff,#toc-title,#mkdocumentprogress,#mkincludechart,#mkprogressbar1,#mkprogressbar2,.mkscrollmeter,#alllinks,.popup{display:none !important}.suppressprintlinks a{border-bottom:none !important;color:inherit !important;cursor:default !important;text-decoration:none !important}.hrefafterlinktext #wrapper a:link:after,.hrefafterlinktext #wrapper a:visited:after{content:" (" attr(href) ") ";font-size:90%;opacity:.9}.nocodebreak pre{page-break-inside:avoid}img,table,figure{page-break-inside:avoid}.breakfootnotes .footnotes{page-break-before:always}.breakfootnotes .footnotes hr{display:none}#mktoctitle{display:block}#print-title{border-bottom:solid 1px #666;display:block}#wrapper pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}#wrapper #generated-toc-clone,#wrapper #mkreplaced-toc{display:block}.task-list{padding-left:3.3rem}.mkstyle--ink .task-list,.mkstyle--swiss .task-list{padding-left:3.3rem !important}.mkstyle--upstandingcitizen .task-list,.mkstyle--github .task-list{padding-left:3.6rem !important}.mkstyle--manuscript .task-list{padding-left:2.4rem !important}.mkstyle--amblin .task-list{padding-left:2.1rem !important}.mkstyle--grump .task-list{padding-left:1rem !important}.mkstyle--grump .task-list .task-list-item-checkbox{left:0 !important}.task-list .task-list-item{list-style-type:none !important;left:auto}.task-list .task-list-item .task-list-item-checkbox{-webkit-appearance:none;position:relative;left:auto}.task-list .task-list-item .task-list-item-checkbox:before{border:solid 1px #aaa;border-radius:2px;color:white;content:' ';display:block;font-weight:bold;height:1em;left:-1rem;line-height:1;position:absolute;text-align:center;top:-.75em;width:1em}.task-list .gh-complete.task-list-item .task-list-item-checkbox:before{background:#838387;content:'\2713'}}

#wrapper #generated-toc-clone,#wrapper #mkreplaced-toc,#wrapper #generated-toc-clone ul,#wrapper #mkreplaced-toc ul{list-style-position:inside}#wrapper #generated-toc-clone li.missing,#wrapper #mkreplaced-toc li.missing{list-style-type: none!important}#wrapper #generated-toc-clone ul,#wrapper #mkreplaced-toc ul{list-style-type: upper-roman}#wrapper #generated-toc-clone>ul>li>ul,#wrapper #mkreplaced-toc>li>ul {list-style-type: decimal}#wrapper #generated-toc-clone>ul>li>ul>li>ul,#wrapper #mkreplaced-toc>li>ul>li>ul{list-style-type: decimal-leading-zero}#wrapper #generated-toc-clone>ul>li>ul>li>ul>li>ul,#wrapper #mkreplaced-toc>li>ul>li>ul>li>ul{list-style-type: lower-greek}#wrapper #generated-toc-clone>ul>li>ul>li>ul>li>ul>li>ul,#wrapper #mkreplaced-toc>li>ul>li>ul>li>ul>li>ul{list-style-type: disc}#wrapper #generated-toc-clone>ul>li>ul>li>ul>li>ul>li>ul>li>ul,#wrapper #mkreplaced-toc>li>ul>li>ul>li>ul>li>ul>li>ul{list-style-type: square}#wrapper #generated-toc-clone,#wrapper #mkreplaced-toc{}

</style>

<style id="mkprintstyles">@media print{#wrapper #generated-toc-clone,#generated-toc{display:none!important}

html,body,#wrapper{font-size:10pt!important}

}

</style>

  </head>

<body class="normal firstload">

  <div id="wrapper">

<h1 id="vue3%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B">Vue3快速上手</h1>

<p><img src="https://user-images.githubusercontent.com/499550/93624428-53932780-f9ae-11ea-8d16-af949e16a09f.png" style="width:200px" /></p>

<h2 id="1.vue3%E7%AE%80%E4%BB%8B">1.Vue3简介</h2>

<ul>

<li>2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)</li>

<li>耗时2年多、<a href="https://github.com/vuejs/vue-next/graphs/commit-activity">2600+次提交</a>、<a href="https://github.com/vuejs/rfcs/tree/master/active-rfcs">30+个RFC</a>、<a href="https://github.com/vuejs/vue-next/pulls?q=is%3Apr+is%3Amerged+-author%3Aapp%2Fdependabot-preview+">600+次PR</a>、<a href="https://github.com/vuejs/vue-next/graphs/contributors">99位贡献者</a></li>

<li>github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0</li>

</ul>

<h2 id="2.vue3%E5%B8%A6%E6%9D%A5%E4%BA%86%E4%BB%80%E4%B9%88">2.Vue3带来了什么</h2>

<h3 id="1.%E6%80%A7%E8%83%BD%E7%9A%84%E6%8F%90%E5%8D%87">1.性能的提升</h3>

<ul>

<li><p>打包大小减少41%</p></li>

<li><p>初次渲染快55%, 更新渲染快133%</p></li>

<li><p>内存减少54%</p></li>

</ul>

<p>&#8230;&#8230;</p>

<h3 id="2.%E6%BA%90%E7%A0%81%E7%9A%84%E5%8D%87%E7%BA%A7">2.源码的升级</h3>

<ul>

<li><p>使用Proxy代替defineProperty实现响应式</p></li>

<li><p>重写虚拟DOM的实现和Tree-Shaking</p></li>

</ul>

<p>&#8230;&#8230;</p>

<h3 id="3.%E6%8B%A5%E6%8A%B1typescript">3.拥抱TypeScript</h3>

<ul>

<li>Vue3可以更好的支持TypeScript</li>

</ul>

<h3 id="4.%E6%96%B0%E7%9A%84%E7%89%B9%E6%80%A7">4.新的特性</h3>

<ol>

<li><p>Composition API(组合API)</p></li>

<li><p>setup配置 </p></li>

<li><p>ref与reactive </p></li>

<li><p>watch与watchEffect </p></li>

<li><p>provide与inject </p></li>

<li><p>&#8230;&#8230; </p></li>

<li><p>新的内置组件 </p></li>

<li><p>Fragment </p></li>

<li><p>Teleport </p></li>

<li><p>Suspense </p></li>

<li><p>其他改变</p></li>

<li><p>新的生命周期钩子 </p></li>

<li><p>data 选项应始终被声明为一个函数 </p></li>

<li><p>移除keyCode支持作为 v-on 的修饰符 </p></li>

<li><p>&#8230;&#8230;</p></li>

</ol>

<h1 id="%E4%B8%80%E3%80%81%E5%88%9B%E5%BB%BAvue3.0%E5%B7%A5%E7%A8%8B">一、创建Vue3.0工程</h1>

<h2 id="1.%E4%BD%BF%E7%94%A8vue-cli%E5%88%9B%E5%BB%BA">1.使用 vue-cli 创建</h2>

<p>官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create</p>

<pre><code class="bash">## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上

vue --version

## 安装或者升级你的@vue/cli

npm install -g @vue/cli

## 创建

vue create vue_test

## 启动

cd vue_test

npm run serve</code></pre>

<h2 id="2.%E4%BD%BF%E7%94%A8vite%E5%88%9B%E5%BB%BA">2.使用 vite 创建</h2>

<p>官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite</p>

<p>vite官网:https://vitejs.cn</p>

<ul>

<li>什么是vite?—— 新一代前端构建工具。</li>

<li>优势如下:</li>

<li>开发环境中,无需打包操作,可快速的冷启动。</li>

<li>轻量快速的热重载(HMR)。</li>

<li>真正的按需编译,不再等待整个应用编译完成。</li>

<li>传统构建 与 vite构建对比图</li>

</ul>

<p><img src="https://cn.vitejs.dev/assets/bundler.37740380.png" style="width:500px;height:280px;float:left" /><img src="https://cn.vitejs.dev/assets/esm.3070012d.png" style="width:480px;height:280px" /></p>

<pre><code class="bash">## 创建工程

npm init vite-app &lt;project-name&gt;

## 进入工程目录

cd &lt;project-name&gt;

## 安装依赖

npm install

## 运行

npm run dev</code></pre>

<h1 id="%E4%BA%8C%E3%80%81%E5%B8%B8%E7%94%A8compositionapi">二、常用 Composition API</h1>

<p>官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html</p>

<h2 id="1.%E6%8B%89%E5%BC%80%E5%BA%8F%E5%B9%95%E7%9A%84setup">1.拉开序幕的setup</h2>

<ol>

<li>理解:Vue3.0中一个新的配置项,值为一个函数。</li>

<li>setup是所有<strong style="color:#DD5145">Composition API(组合API)</strong><i style="color:gray;font-weight:bold">“ 表演的舞台 ”</i>。</li>

<li>组件中所用到的:数据、方法等等,均要配置在setup中。</li>

<li>setup函数的两种返回值:</li>

<li>若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)</li>

<li><span style="color:#aad">若返回一个渲染函数:则可以自定义渲染内容。(了解)</span></li>

<li>注意点:</li>

<li>尽量不要与Vue2.x配置混用

<ul>

<li>Vue2.x配置(data、methos、computed&#8230;)中<strong style="color:#DD5145">可以访问到</strong>setup中的属性、方法。</li>

<li>但在setup中<strong style="color:#DD5145">不能访问到</strong>Vue2.x配置(data、methos、computed&#8230;)。</li>

<li>如果有重名, setup优先。</li>

</ul></li>

<li>setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)</li>

</ol>

<h2 id="2.ref%E5%87%BD%E6%95%B0">2.ref函数</h2>

<ul>

<li>作用: 定义一个响应式的数据</li>

<li>语法: <code>const xxx = ref(initValue)</code></li>

<li>创建一个包含响应式数据的<strong style="color:#DD5145">引用对象(reference对象,简称ref对象)</strong>。</li>

<li>JS中操作数据: <code>xxx.value</code></li>

<li>模板中读取数据: 不需要.value,直接:<code>&lt;div&gt;{{xxx}}&lt;/div&gt;</code></li>

<li>备注:</li>

<li>接收的数据可以是:基本类型、也可以是对象类型。</li>

<li>基本类型的数据:响应式依然是靠<code>Object.defineProperty()</code>的<code>get</code>与<code>set</code>完成的。</li>

<li>对象类型的数据:内部 <i style="color:gray;font-weight:bold">“ 求助 ”</i> 了Vue3.0中的一个新函数—— <code>reactive</code>函数。</li>

</ul>

<h2 id="3.reactive%E5%87%BD%E6%95%B0">3.reactive函数</h2>

<ul>

<li>作用: 定义一个<strong style="color:#DD5145">对象类型</strong>的响应式数据(基本类型不要用它,要用<code>ref</code>函数)</li>

<li>语法:<code>const 代理对象= reactive(源对象)</code>接收一个对象(或数组),返回一个<strong style="color:#DD5145">代理对象(Proxy的实例对象,简称proxy对象)</strong></li>

<li>reactive定义的响应式数据是“深层次的”。</li>

<li>内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。</li>

</ul>

<h2 id="4.vue3.0%E4%B8%AD%E7%9A%84%E5%93%8D%E5%BA%94%E5%BC%8F%E5%8E%9F%E7%90%86">4.Vue3.0中的响应式原理</h2>

<h3 id="vue2.x%E7%9A%84%E5%93%8D%E5%BA%94%E5%BC%8F">vue2.x的响应式</h3>

<ul>

<li><p>实现原理: </p></li>

<li><p>对象类型:通过<code>Object.defineProperty()</code>对属性的读取、修改进行拦截(数据劫持)。</p></li>

<li><p>数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。</p>

<pre><code class="js">Object.defineProperty(data, &apos;count&apos;, {

    get () {},

    set () {}

})</code></pre></li>

<li><p>存在问题: </p></li>

<li><p>新增属性、删除属性, 界面不会更新。 </p></li>

<li><p>直接通过下标修改数组, 界面不会自动更新。</p></li>

</ul>

<h3 id="vue3.0%E7%9A%84%E5%93%8D%E5%BA%94%E5%BC%8F">Vue3.0的响应式</h3>

<ul>

<li><p>实现原理: </p></li>

<li><p>通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。 </p></li>

<li><p>通过Reflect(反射): 对源对象的属性进行操作。 </p></li>

<li>MDN文档中描述的Proxy与Reflect:

<ul>

<li><p>Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy</p></li>

<li><p>Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect</p></li>

</ul>

<p><pre><code class="js">new Proxy(data, {

    // 拦截读取属性值

    get (target, prop) {

        return Reflect.get(target, prop)

    },

    // 拦截设置属性值或添加新属性

    set (target, prop, value) {

        return Reflect.set(target, prop, value)

    },

    // 拦截删除属性

    deleteProperty (target, prop) {

        return Reflect.deleteProperty(target, prop)

    }

})

proxy.name = &apos;tom&apos;  </code></pre></p></li>

</ul>

<h2 id="5.reactive%E5%AF%B9%E6%AF%94ref">5.reactive对比ref</h2>

<ul>

<li>从定义数据角度对比:</li>

<li>ref用来定义:<strong style="color:#DD5145">基本类型数据</strong>。</li>

<li>reactive用来定义:<strong style="color:#DD5145">对象(或数组)类型数据</strong>。</li>

<li>备注:ref也可以用来定义<strong style="color:#DD5145">对象(或数组)类型数据</strong>, 它内部会自动通过<code>reactive</code>转为<strong style="color:#DD5145">代理对象</strong>。</li>

<li>从原理角度对比:</li>

<li>ref通过<code>Object.defineProperty()</code>的<code>get</code>与<code>set</code>来实现响应式(数据劫持)。</li>

<li>reactive通过使用<strong style="color:#DD5145">Proxy</strong>来实现响应式(数据劫持), 并通过<strong style="color:#DD5145">Reflect</strong>操作<strong style="color:orange">源对象</strong>内部的数据。</li>

<li>从使用角度对比:</li>

<li>ref定义的数据:操作数据<strong style="color:#DD5145">需要</strong><code>.value</code>,读取数据时模板中直接读取<strong style="color:#DD5145">不需要</strong><code>.value</code>。</li>

<li>reactive定义的数据:操作数据与读取数据:<strong style="color:#DD5145">均不需要</strong><code>.value</code>。</li>

</ul>

<h2 id="6.setup%E7%9A%84%E4%B8%A4%E4%B8%AA%E6%B3%A8%E6%84%8F%E7%82%B9">6.setup的两个注意点</h2>

<ul>

<li><p>setup执行的时机 </p></li>

<li><p>在beforeCreate之前执行一次,this是undefined。</p></li>

<li><p>setup的参数 </p></li>

<li><p>props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。 </p></li>

<li>context:上下文对象

<ul>

<li>attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 <code>this.$attrs</code>。</li>

<li>slots: 收到的插槽内容, 相当于 <code>this.$slots</code>。</li>

<li>emit: 分发自定义事件的函数, 相当于 <code>this.$emit</code>。</li>

</ul></li>

</ul>

<h2 id="7.%E8%AE%A1%E7%AE%97%E5%B1%9E%E6%80%A7%E4%B8%8E%E7%9B%91%E8%A7%86">7.计算属性与监视</h2>

<h3 id="1.computed%E5%87%BD%E6%95%B0">1.computed函数</h3>

<ul>

<li><p>与Vue2.x中computed配置功能一致</p></li>

<li><p>写法</p></li>

</ul>

<p><pre><code class="js">import {computed} from &apos;vue&apos;

setup(){

    ...

    //计算属性&mdash;&mdash;简写

    let fullName = computed(()=&gt;{

        return person.firstName + &apos;-&apos; + person.lastName

    })

    //计算属性&mdash;&mdash;完整

    let fullName = computed({

        get(){

            return person.firstName + &apos;-&apos; + person.lastName

        },

        set(value){

            const nameArr = value.split(&apos;-&apos;)

            person.firstName = nameArr[0]

            person.lastName = nameArr[1]

        }

    })

}</code></pre></p>

<h3 id="2.watch%E5%87%BD%E6%95%B0">2.watch函数</h3>

<ul>

<li><p>与Vue2.x中watch配置功能一致</p></li>

<li><p>两个小“坑”:</p></li>

<li><p>监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。 </p></li>

<li><p>监视reactive定义的响应式数据中某个属性时:deep配置有效。</p></li>

</ul>

<p><pre><code class="js">//情况一:监视ref定义的响应式数据

watch(sum,(newValue,oldValue)=&gt;{

    console.log(&apos;sum变化了&apos;,newValue,oldValue)

},{immediate:true})

//情况二:监视多个ref定义的响应式数据

watch([sum,msg],(newValue,oldValue)=&gt;{

    console.log(&apos;sum或msg变化了&apos;,newValue,oldValue)

})

/* 情况三:监视reactive定义的响应式数据

            若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!

            若watch监视的是reactive定义的响应式数据,则强制开启了深度监视

*/

watch(person,(newValue,oldValue)=&gt;{

    console.log(&apos;person变化了&apos;,newValue,oldValue)

},{immediate:true,deep:false}) //此处的deep配置不再奏效

//情况四:监视reactive定义的响应式数据中的某个属性

watch(()=&gt;person.job,(newValue,oldValue)=&gt;{

    console.log(&apos;person的job变化了&apos;,newValue,oldValue)

},{immediate:true,deep:true})

//情况五:监视reactive定义的响应式数据中的某些属性

watch([()=&gt;person.job,()=&gt;person.name],(newValue,oldValue)=&gt;{

    console.log(&apos;person的job变化了&apos;,newValue,oldValue)

},{immediate:true,deep:true})

//特殊情况

watch(()=&gt;person.job,(newValue,oldValue)=&gt;{

    console.log(&apos;person的job变化了&apos;,newValue,oldValue)

},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效</code></pre></p>

<h3 id="3.watcheffect%E5%87%BD%E6%95%B0">3.watchEffect函数</h3>

<ul>

<li><p>watch的套路是:既要指明监视的属性,也要指明监视的回调。</p></li>

<li><p>watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。</p></li>

<li><p>watchEffect有点像computed:</p></li>

<li><p>但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。 </p></li>

<li><p>而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。</p></li>

</ul>

<p><pre><code class="js">//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。

watchEffect(()=&gt;{

    const x1 = sum.value

    const x2 = person.age

    console.log(&apos;watchEffect配置的回调执行了&apos;)

})</code></pre></p>

<h2 id="8.%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">8.生命周期</h2>

<div style="border:1px solid black;width:380px;float:left;margin-right:20px;"><strong>vue2.x的生命周期</strong><img src="https://cn.vuejs.org/images/lifecycle.png" alt="lifecycle_2" style="zoom:33%;width:1200px" /></div><div style="border:1px solid black;width:510px;height:985px;float:left"><strong>vue3.0的生命周期</strong><img src="https://v3.cn.vuejs.org/images/lifecycle.svg" alt="lifecycle_2" style="zoom:33%;width:2500px" /></div>

<p>1</p>

<ul>

<li>Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:</li>

<li><code>beforeDestroy</code>改名为 <code>beforeUnmount</code></li>

<li><code>destroyed</code>改名为 <code>unmounted</code></li>

<li>Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:</li>

<li><code>beforeCreate</code>===&gt;<code>setup()</code></li>

<li><code>created</code>=======&gt;<code>setup()</code></li>

<li><code>beforeMount</code> ===&gt;<code>onBeforeMount</code></li>

<li><code>mounted</code>=======&gt;<code>onMounted</code></li>

<li><code>beforeUpdate</code>===&gt;<code>onBeforeUpdate</code></li>

<li><code>updated</code> =======&gt;<code>onUpdated</code></li>

<li><code>beforeUnmount</code> ==&gt;<code>onBeforeUnmount</code></li>

<li><code>unmounted</code> =====&gt;<code>onUnmounted</code></li>

</ul>

<h2 id="9.%E8%87%AA%E5%AE%9A%E4%B9%89hook%E5%87%BD%E6%95%B0">9.自定义hook函数</h2>

<ul>

<li><p>什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。</p></li>

<li><p>类似于vue2.x中的mixin。</p></li>

<li><p>自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。</p></li>

</ul>

<h2 id="10.toref">10.toRef</h2>

<ul>

<li><p>作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。 </p></li>

<li><p>语法:<code>const name = toRef(person,'name')</code> </p></li>

<li><p>应用: 要将响应式对象中的某个属性单独提供给外部使用时。</p></li>

<li><p>扩展:<code>toRefs</code> 与<code>toRef</code>功能一致,但可以批量创建多个 ref 对象,语法:<code>toRefs(person)</code></p></li>

</ul>

<h1 id="%E4%B8%89%E3%80%81%E5%85%B6%E5%AE%83compositionapi">三、其它 Composition API</h1>

<h2 id="1.shallowreactive%E4%B8%8Eshallowref">1.shallowReactive 与 shallowRef</h2>

<ul>

<li><p>shallowReactive:只处理对象最外层属性的响应式(浅响应式)。 </p></li>

<li><p>shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。</p></li>

<li><p>什么时候使用? </p></li>

<li><p>如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===&gt; shallowReactive。 </p></li>

<li><p>如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===&gt; shallowRef。</p></li>

</ul>

<h2 id="2.readonly%E4%B8%8Eshallowreadonly">2.readonly 与 shallowReadonly</h2>

<ul>

<li>readonly: 让一个响应式数据变为只读的(深只读)。</li>

<li>shallowReadonly:让一个响应式数据变为只读的(浅只读)。</li>

<li>应用场景: 不希望数据被修改时。</li>

</ul>

<h2 id="3.toraw%E4%B8%8Emarkraw">3.toRaw 与 markRaw</h2>

<ul>

<li>toRaw:</li>

<li>作用:将一个由<code>reactive</code>生成的<strong style="color:orange">响应式对象</strong>转为<strong style="color:orange">普通对象</strong>。</li>

<li>使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。</li>

<li>markRaw:</li>

<li>作用:标记一个对象,使其永远不会再成为响应式对象。</li>

<li>应用场景:

<ol>

<li>有些值不应被设置为响应式的,例如复杂的第三方类库等。</li>

<li>当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。</li>

</ol></li>

</ul>

<h2 id="4.customref">4.customRef</h2>

<ul>

<li><p>作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。</p></li>

<li><p>实现防抖效果:</p></li>

</ul>

<p><pre><code class="vue">&lt;template&gt;

    &lt;input type=&quot;text&quot; v-model=&quot;keyword&quot;&gt;

    &lt;h3&gt;{{keyword}}&lt;/h3&gt;

&lt;/template&gt;

&lt;script&gt;

    import {ref,customRef} from &apos;vue&apos;

    export default {

        name:&apos;Demo&apos;,

        setup(){

            // let keyword = ref(&apos;hello&apos;) //使用Vue准备好的内置ref

            //自定义一个myRef

            function myRef(value,delay){

                let timer

                //通过customRef去实现自定义

                return customRef((track,trigger)=&gt;{

                    return{

                        get(){

                            track() //告诉Vue这个value值是需要被&ldquo;追踪&rdquo;的

                            return value

                        },

                        set(newValue){

                            clearTimeout(timer)

                            timer = setTimeout(()=&gt;{

                                value = newValue

                                trigger() //告诉Vue去更新界面

                            },delay)

                        }

                    }

                })

            }

            let keyword = myRef(&apos;hello&apos;,500) //使用程序员自定义的ref

            return {

                keyword

            }

        }

    }

&lt;/script&gt;</code></pre></p>

<h2 id="5.provide%E4%B8%8Einject">5.provide 与 inject</h2>

<p><img src="https://v3.cn.vuejs.org/images/components_provide.png" style="width:300px" /></p>

<ul>

<li><p>作用:实现<strong style="color:#DD5145">祖与后代组件间</strong>通信</p></li>

<li><p>套路:父组件有一个 <code>provide</code> 选项来提供数据,后代组件有一个 <code>inject</code> 选项来开始使用这些数据</p></li>

<li><p>具体写法:</p></li>

<li><p>祖组件中:</p>

<p><pre><code class="js">setup(){

    ......

    let car = reactive({name:&apos;奔驰&apos;,price:&apos;40万&apos;})

    provide(&apos;car&apos;,car)

    ......

}</code></pre></p></li>

<li><p>后代组件中:</p>

<p><pre><code class="js">setup(props,context){

    ......

    const car = inject(&apos;car&apos;)

    return {car}

    ......

}</code></pre></p></li>

</ul>

<h2 id="6.%E5%93%8D%E5%BA%94%E5%BC%8F%E6%95%B0%E6%8D%AE%E7%9A%84%E5%88%A4%E6%96%AD">6.响应式数据的判断</h2>

<ul>

<li>isRef: 检查一个值是否为一个 ref 对象</li>

<li>isReactive: 检查一个对象是否是由 <code>reactive</code> 创建的响应式代理</li>

<li>isReadonly: 检查一个对象是否是由 <code>readonly</code> 创建的只读代理</li>

<li>isProxy: 检查一个对象是否是由 <code>reactive</code> 或者 <code>readonly</code> 方法创建的代理</li>

</ul>

<h1 id="%E5%9B%9B%E3%80%81compositionapi%E7%9A%84%E4%BC%98%E5%8A%BF">四、Composition API 的优势</h1>

<h2 id="1.optionsapi%E5%AD%98%E5%9C%A8%E7%9A%84%E9%97%AE%E9%A2%98">1.Options API 存在的问题</h2>

<p>使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。</p>

<div style="width:600px;height:370px;overflow:hidden;float:left">

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f84e4e2c02424d9a99862ade0a2e4114~tplv-k3u1fbpfcp-watermark.image" style="width:600px;float:left" />

</div>

<div style="width:300px;height:370px;overflow:hidden;float:left">

<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5ac7e20d1784887a826f6360768a368~tplv-k3u1fbpfcp-watermark.image" style="zoom:50%;width:560px;left" />

</div>

<h2 id="2.compositionapi%E7%9A%84%E4%BC%98%E5%8A%BF">2.Composition API 的优势</h2>

<p>我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。</p>

<div style="width:500px;height:340px;overflow:hidden;float:left">

<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc0be8211fc54b6c941c036791ba4efe~tplv-k3u1fbpfcp-watermark.image"style="height:360px"/>

</div>

<div style="width:430px;height:340px;overflow:hidden;float:left">

<img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cc55165c0e34069a75fe36f8712eb80~tplv-k3u1fbpfcp-watermark.image"style="height:360px"/>

</div>

<h1 id="%E4%BA%94%E3%80%81%E6%96%B0%E7%9A%84%E7%BB%84%E4%BB%B6">五、新的组件</h1>

<h2 id="1.fragment">1.Fragment</h2>

<ul>

<li>在Vue2中: 组件必须有一个根标签</li>

<li>在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中</li>

<li>好处: 减少标签层级, 减小内存占用</li>

</ul>

<h2 id="2.teleport">2.Teleport</h2>

<ul>

<li>什么是Teleport?—— <code>Teleport</code> 是一种能够将我们的<strong style="color:#DD5145">组件html结构</strong>移动到指定位置的技术。</li>

</ul>

<p><pre><code class="vue">&lt;teleport to=&quot;移动位置&quot;&gt;

    &lt;div v-if=&quot;isShow&quot; class=&quot;mask&quot;&gt;

        &lt;div class=&quot;dialog&quot;&gt;

            &lt;h3&gt;我是一个弹窗&lt;/h3&gt;

            &lt;button @click=&quot;isShow = false&quot;&gt;关闭弹窗&lt;/button&gt;

        &lt;/div&gt;

    &lt;/div&gt;

&lt;/teleport&gt;</code></pre></p>

<h2 id="3.suspense">3.Suspense</h2>

<ul>

<li><p>等待异步组件时渲染一些额外内容,让应用有更好的用户体验</p></li>

<li><p>使用步骤:</p></li>

<li><p>异步引入组件</p>

<pre><code class="js">import {defineAsyncComponent} from &apos;vue&apos;

const Child = defineAsyncComponent(()=&gt;import(&apos;./components/Child.vue&apos;))</code></pre></li>

<li><p>使用<code>Suspense</code>包裹组件,并配置好<code>default</code> 与 <code>fallback</code></p>

<pre><code class="vue">&lt;template&gt;

    &lt;div class=&quot;app&quot;&gt;

        &lt;h3&gt;我是App组件&lt;/h3&gt;

        &lt;Suspense&gt;

            &lt;template v-slot:default&gt;

                &lt;Child/&gt;

            &lt;/template&gt;

            &lt;template v-slot:fallback&gt;

                &lt;h3&gt;加载中.....&lt;/h3&gt;

            &lt;/template&gt;

        &lt;/Suspense&gt;

    &lt;/div&gt;

&lt;/template&gt;</code></pre></li>

</ul>

<h1 id="%E5%85%AD%E3%80%81%E5%85%B6%E4%BB%96">六、其他</h1>

<h2 id="1.%E5%85%A8%E5%B1%80api%E7%9A%84%E8%BD%AC%E7%A7%BB">1.全局API的转移</h2>

<ul>

<li><p>Vue 2.x 有许多全局 API 和配置。 </p></li>

<li><p>例如:注册全局组件、注册全局指令等。</p>

<pre><code class="js">//注册全局组件

Vue.component(&apos;MyButton&apos;, {

  data: () =&gt; ({

    count: 0

  }),

  template: &apos;&lt;button @click=&quot;count++&quot;&gt;Clicked {{ count }} times.&lt;/button&gt;&apos;

})

//注册全局指令

Vue.directive(&apos;focus&apos;, {

  inserted: el =&gt; el.focus()

}</code></pre></li>

<li><p>Vue3.0中对这些API做出了调整:</p></li>

<li><p>将全局的API,即:<code>Vue.xxx</code>调整到应用实例(<code>app</code>)上</p>

<table>

<colgroup>

<col style="text-align:left;"/>

<col style="text-align:left;"/>

</colgroup>

<thead>

<tr>

    <th style="text-align:left;">2.x 全局 API(<code>Vue</code>)</th>

    <th style="text-align:left;">3.x 实例 API (<code>app</code>)</th>

</tr>

</thead>

<tbody>

<tr>

    <td style="text-align:left;">Vue.config.xxxx</td>

    <td style="text-align:left;">app.config.xxxx</td>

</tr>

<tr>

    <td style="text-align:left;">Vue.config.productionTip</td>

    <td style="text-align:left;"><strong style="color:#DD5145">移除</strong></td>

</tr>

<tr>

    <td style="text-align:left;">Vue.component</td>

    <td style="text-align:left;">app.component</td>

</tr>

<tr>

    <td style="text-align:left;">Vue.directive</td>

    <td style="text-align:left;">app.directive</td>

</tr>

<tr>

    <td style="text-align:left;">Vue.mixin</td>

    <td style="text-align:left;">app.mixin</td>

</tr>

<tr>

    <td style="text-align:left;">Vue.use</td>

    <td style="text-align:left;">app.use</td>

</tr>

<tr>

    <td style="text-align:left;">Vue.prototype</td>

    <td style="text-align:left;">app.config.globalProperties</td>

</tr>

</tbody>

</table>

</li>

</ul>

<h2 id="2.%E5%85%B6%E4%BB%96%E6%94%B9%E5%8F%98">2.其他改变</h2>

<ul>

<li><p>data选项应始终被声明为一个函数。</p></li>

<li><p>过度类名的更改:</p></li>

<li><p>Vue2.x写法</p>

<pre><code class="css">.v-enter,

.v-leave-to {

  opacity: 0;

}

.v-leave,

.v-enter-to {

  opacity: 1;

}</code></pre></li>

<li><p>Vue3.x写法</p>

<pre><code class="css">.v-enter-from,

.v-leave-to {

  opacity: 0;

}

.v-leave-from,

.v-enter-to {

  opacity: 1;

}</code></pre></li>

<li><p><strong style="color:#DD5145">移除</strong>keyCode作为 v-on 的修饰符,同时也不再支持<code>config.keyCodes</code></p></li>

<li><p><strong style="color:#DD5145">移除</strong><code>v-on.native</code>修饰符</p></li>

<li><p>父组件中绑定事件</p>

<pre><code class="vue">&lt;my-component

  v-on:close=&quot;handleComponentEvent&quot;

  v-on:click=&quot;handleNativeClickEvent&quot;

/&gt;</code></pre></li>

<li><p>子组件中声明自定义事件</p>

<pre><code class="vue">&lt;script&gt;

  export default {

    emits: [&apos;close&apos;]

  }

&lt;/script&gt;</code></pre></li>

<li><p><strong style="color:#DD5145">移除</strong>过滤器(filter)</p></li>

</ul>

<blockquote>

<p>过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。</p>

</blockquote>

<ul>

<li>&#8230;&#8230;</li>

</ul>

<!-- ##END MARKED WRAPPER## -->

    </div>

</body>

</html>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容