diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
commit | 33058f2b292b3a581333bdfb21b8f671898c5060 (patch) | |
tree | 51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/learn/tools_and_testing | |
parent | 8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff) | |
download | translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2 translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip |
initial commit
Diffstat (limited to 'files/zh-cn/learn/tools_and_testing')
25 files changed, 9207 insertions, 0 deletions
diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/index.html new file mode 100644 index 0000000000..affc6f3aec --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/index.html @@ -0,0 +1,136 @@ +--- +title: 理解客户端JavaScript框架 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks +tags: + - JavaScript + - 初学者 + - 前段框架 + - 学习 + - 客户端 +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">JavaScript框架是现代前端web开发的重要部分,为开发人员提供了构建可伸缩、交互式web应用程序的经过测试的工具。许多现代公司使用框架作为工具的标准部分,因此许多前端开发工作现在需要框架经验。</p> + +<p class="summary">作为一个有抱负的前端开发人员,很难找出从哪里开始学习框架——有这么多不同的框架可供选择,新的问题出现,他们大多以类似的方式工作,但做一些不同的事情,还有一些具体的事情时要小心使用框架。</p> + +<p class="summary">在这一系列文章中,我们旨在为您提供一个舒适的起点,帮助您开始学习框架。我们的目标不是详尽地教给你所有你需要知道的关于React/ReactDOM,或者Vue,或者其他一些特定的框架;框架团队自己的文档已经完成了这项工作。相反,我们想备份和首先回答更基本的问题,如:</p> + +<ul> + <li class="summary">为什么要使用框架?他们为我解决了什么问题?</li> + <li class="summary">当我尝试选择一个框架时,我应该问什么问题?我甚至需要使用框架吗?</li> + <li class="summary">框架有什么特性?它们一般是如何工作的?框架对这些特性的实现有何不同?</li> + <li class="summary">它们与“普通的”JavaScript或HTML有什么关系?</li> +</ul> + +<p class="summary">在此之后,我们将提供一些教程,介绍一些主要框架的基本内容,为您提供足够的上下文和熟悉感,以便您自己开始更深入地学习。我们希望你以一种实用的方式来学习框架,不要忘记web平台的基本最佳实践,比如可访问性。</p> + +<p class="summary"><strong><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">通过“客户端框架介绍”即刻开始</a></strong></p> + +<h2 id="前提条件">前提条件</h2> + +<p>在尝试学习客户端框架(HTML、CSS,特别是JavaScript)之前,您应该首先真正学习核心web语言的基础知识。</p> + +<p>如果你理解了框架底层的基础Web平台特性,你将能编写更加健壮和专业的代码,更容易地排除问题。</p> + +<h2 id="说明指导">说明指导</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">1. 客户端框架介绍</a></dt> + <dd>我们首先从以下话题开始研究框架,框架领域的总览,JavaScript和框架历史的简介,为什么需要框架以及框架能带给我们什么,如何考虑选择一个框架开始学习,有什么其它的客户端框架可供选择。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">2. 框架的主要特性</a></dt> + <dd>每种JavaScript框架都有自己不同的方式更新DOM,处理浏览器事件,为开发者提供愉悦的使用体验,这篇文章将探索“四大”框架的主要特性,从高级角度探讨框架的工作方式以及它们之间的区别。</dd> +</dl> + +<h2 id="React教程">React教程</h2> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: React教程最后测试于2020年5月,基于React/ReactDOM 16.13.1和create-react-app 3.4.1.</p> + +<p>如果你需要确认你的代码与我们的版本区别,你可以在我们的<a href="https://github.com/mdn/todo-react">todo-react repository</a>仓库找到React应用示例代码的完整版本。想要获取当前最新的版本,查看<a href="https://mdn.github.io/todo-react-build/">https://mdn.github.io/todo-react-build/</a>.</p> +</div> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">1. 开始使用React</a></dt> + <dd>此章节我们将向React打招呼。我们将能发现有关React的背景和使用场景的一些细节,在我们的计算机上设置基本的React工具链,创建和运行简单的入门应用,学习React在这个过程是如何工作的。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">2. 开始我们的React待办清单</a></dt> + <dd>假设我们计划使用React创建proof-of-concept,一个允许用户添加,编辑和删除工作任务,不删除的情况标记任务完成的应用。此章节将带你完成基本的<code>App</code>组件结构和样式,为最后即将添加的独立组件定义和交互做好准备。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">3. 组件化我们的React应用</a></dt> + <dd>到这个阶段,我们的应用仍是一个单一结构。在让它工作之前,我们需要将它分解为可管理的描述性组件。React对于组件没有任何硬性规定--这完全取决于你!在此章节我们将向你展示一个合理的方式来将我们的应用分解成 组件。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">4. React交互:事件和状态</a></dt> + <dd>我们的组件化计划已经完成,现在是时候开始将我们的应用从一个完全静态的界面更新成允许交互和变更的界面。在这个章节我们将继续研究事件和状态,借此实现上述功能。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">5. React交互:编辑,过滤,条件渲染</a></dt> + <dd>我们已经接近React旅程的终点(至少目前是这样),我们将对我们的Todo列表应用的主要功能区域做最后的更改。这包括允许编辑现有的任务,在所有列表,已完成列表和未完成列表中过滤任务。在这个过程中我们将研究条件渲染。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">6. React的可访问性支持</a></dt> + <dd>在我们最后的教程章节,我们将重点介绍可访问性,包括React的焦点管理,这可以提高可用性,降低纯键盘用户和屏幕阅读器用户的困扰。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">7. React资源</a></dt> + <dd>我们的最后章节提供了一个React资源列表,供你用于进一步的学习。</dd> +</dl> + +<h2 id="Ember教程">Ember教程</h2> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Ember教程最后测试于2020年5月,基于Ember/Ember CLI version 3.18.0.</p> + +<p>如果你需要确认你的代码与我们的版本区别,你可以在<a href="https://github.com/NullVoxPopuli/ember-todomvc-tutorial/tree/master/steps/00-finished-todomvc/todomvc">ember-todomvc-tutorial repository</a>找到Ember应用示例代码的完整版本。想要获取当前最新的版本,查看<a href="https://nullvoxpopuli.github.io/ember-todomvc-tutorial/">https://nullvoxpopuli.github.io/ember-todomvc-tutorial/</a> (这还包含了本教程未覆盖到的一些额外的特性)。</p> +</div> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">1. 开始使用Ember</a></dt> + <dd>在我们的Ember第一章节中我们将了解Ember的工作原理及其用途,本地安装Ember工具链,创建一个示例应用,然后做一些初始化设置以便开始开发。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">2. Ember应用结构和组件化</a></dt> + <dd>在此章节,我们将继续规划我们的TodoMVC Ember应用,为其添加HTML部分,随后将这些HTML分解为组件。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">3. Ember交互:事件,类和状态</a></dt> + <dd>至此,我们将添加一些交互行为到我们的应用,使其能够添加和显示新的待办事项。在此过程中,我们将研究如何在Ember中使用事件,创建包含JavaScript代码的组件类来控制交互功能,并且设置服务来跟踪我们的应用的数据状态。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">4. Ember交互:页脚功能,条件渲染</a></dt> + <dd>现在是时候开始在我们的应用中处理页脚功能了。这里我们将更新待办事项计数器以正确显示待完成待办事项的数量,并正确地为已完成事项应用样式(i.e. 对应列表项的复选框处于选中状态)。我们还将实装我们的“Clear completed”按钮。在这个过程中,我们将学习在我们的模板中使用条件渲染。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">5. Ember路由</a></dt> + <dd>在此章节我们学习路由,有时也称为URL-based过滤。我们将使用它来为三个待办视图------"All","Active"和"Completed"提供全局唯一的URL。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">6. Ember资源和疑难解答</a></dt> + <dd>我们的最后一个Ember章节提供了一个供你进一步学习的资源列表,还有一些有用的疑难解答和其他信息。</dd> +</dl> + +<h2 id="Vue教程">Vue教程</h2> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Vue教程最后测试于2020年5月,基于Vue 2.6.11.</p> + +<p>如果你需要确认你的代码与我们的版本区别,你可以在<a href="https://github.com/mdn/todo-vue">todo-vue repository</a>找到Vue应用示例代码的完整版本。想要获取当前当前最新的版本,查看<a href="https://mdn.github.io/todo-vue/dist/">https://mdn.github.io/todo-vue/dist/</a>.</p> +</div> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">1. 开始使用Vue</a></dt> + <dd>现在让我们开始介绍Vue,我们的第三个框架。在此章节我们将简单了解Vue的背景,学习如何安装Vue以及如何创建一个新项目,学习整个项目的高级架构以及独立的组件,了解如何在本地运行这个项目,并准备开始构建我们的示例。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">2. 创建我们的第一个Vue组件</a></dt> + <dd>现在是时候深入Vue,创建我们自己的自定义组件了--我们将从创建一个用来展示待办列表项目的组件开始。在这个过程中,我们将学习一些重要的概念例如在一个组件中调用另一个组件,通过props传递数据给另一个组件并保存数据状态。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">3. 渲染Vue组件列表</a></dt> + <dd><span class="author-d-1gg9uz65z1iz85zgdz68zmqkz84zo2qoxwoxz78zz83zz84zz69z2z80zgwxsgnz83zfkt5e5tz70zz68zmsnjz122zz71z">至此我们已经得到了一个完全可用的组件,我们现在已经准备好添加多个<code>ToDoItem</code> 组件到我们的应用中了。在此章节我们将了解如何添加一组待办项数据到我们的<code>App.vue</code>组件,这组数据我们随后使用<code>v-for</code>指令将它们循环并显示到<code>ToDoItem</code>组件中。</span></dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">4. 添加一个新的待办表单:Vue事件,方法和模型</a></dt> + <dd>现在我们的应用已经有了示例数据和一个循环来将每条数据渲染到<code>ToDoItem</code>中。下一步我们要做的是让用户能够输入他们自己的待办事项到这个应用中。 为此我们需要一个文本输入框<code><input></code>,一个在数据提交时触发的事件,一个在数据提交时被触发来添加数据和渲染列表的方法和一个用来控制数据的模型。这就是这个章节我们将要介绍的内容。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">5. 使用CSS美化Vue组件</a></dt> + <dd>终于到了美化我们的应用的时间了。在此章节我们将探索用CSS美化Vue组件的不同方法。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">6. 使用Vue计算属性</a></dt> + <dd>在这个章节我们将添加一个显示已完成待办项目数量的计数器,使用Vue的一个叫做计算属性的特性。计算属性与方法相似,但是只有它的依赖内容变更时才会再次运作。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">7. Vue条件渲染:编辑既有待办项</a></dt> + <dd>现在是时候加入我们还未实现一个主要功能部分了------编辑既有待办项。为了实现这个功能,我们将利用Vue的条件渲染能力------<code>v-if</code>和<code>v-else</code>来使既有待办项目在查看状态和文本编辑状态之间切换。我们还将添加删除待办项的功能。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">8. 使用Vue refs进行焦点管理</a></dt> + <dd>我们快要完成Vue的使用了。最后要看的一个功能是焦点管理,换种说法,如何改善我们的应用的键盘可操作性。我们将解决这个问题,通过Vue refs------一个允许你直接操作虚拟DOM的底层DOM,或在组件中直接操作其子组件内部DOM结构的高级特性。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">9. Vue资源</a></dt> + <dd>我们将提供一个你可以用来进一步学习的资源列表来圆满结束我们的Vue学习教程,从这个列表中你也可以获取一些有用的技巧。</dd> +</dl> + +<h2 id="我们应该选择哪个框架?">我们应该选择哪个框架?</h2> + +<p>我们在这里发布专注于主要三大框架的系列说明文章------React/ReactDOM,Ember和Vue的一些原因如下:</p> + +<ul> + <li>它们在今后的一段时间内都将是最受欢迎的选择------就像使用任何软件工具一样,依附于一个处于活跃开发状态并且不太会突然停止开发的工具是一个明智的选择,并且它们会成为你寻找工作时所需的技能之一。</li> + <li>它们拥有强大的社区和良好的文档支持。学习复杂的主题时得到帮助是很重要的,特别是你是刚开始学习时。</li> + <li>我们没有资源能覆盖所有现代框架。维护所有现代框架的最新列表是非常困难的,因为总有新的框架在不断被创造出来。</li> + <li>作为一个初学者,在大量的可选内容中选择关注哪部分是很难的,维持一个相对短的列表反而是有益的。</li> +</ul> + +<p>我们想在前面说一下------我们<strong>没有</strong>因为我们认为它们是最好的或者我们赞同它们而选择这些我们正在关注的框架。我们只是认为它们在上面这些方面评价比较高。</p> + +<p>值得一提的是我们也希望在我们的初版发布时能够包含更多框架的内容,但是我们最终决定先把初版内容发布出来随后在此基础上再慢慢添加更多框架内容,而不是延后更长的时间。如果你最喜欢的框架没有包含在这部分内容中而且你想帮助改善这个情况,请立即告诉我们!你可以通过<a href="https://wiki.mozilla.org/Matrix">Matrix</a>或<a href="https://discourse.mozilla.org/c/mdn">Discourse</a>联系我们或者发送邮件到我们的邮件列表<a href="mailto:mdn-admins@mozilla.org">mdn-admins list</a>。</p> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/main_features/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/main_features/index.html new file mode 100644 index 0000000000..ac7e2a6859 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/main_features/index.html @@ -0,0 +1,354 @@ +--- +title: 框架的主要特性 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">每种JavaScript框架都有自己不同的方式更新DOM,处理浏览器事件,为开发者提供愉快的使用体验,这篇文章将探索“四大”框架的主要特性,从高级角度探讨框架的工作方式以及它们之间的区别。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉核心的 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a> 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解框架主要代码的特性。</td> + </tr> + </tbody> +</table> + +<h2 id="Domain-specific_languages">Domain-specific languages</h2> + +<p>All of the frameworks discussed in this module are powered by JavaScript, and all allow you to use domain-specific languages (DSLs) in order to build your applications. In particular, React has popularized the use of <strong>JSX</strong> for writing its components, while Ember utilizes <strong>Handlebars</strong>. Unlike HTML, these languages know how to read data variables, and this data can be used to streamline the process of writing your UI.</p> + +<p>Angular apps often make heavy use of <strong>TypeScript</strong>. TypeScript is not concerned with the writing of user interfaces, but it is a domain-specific language, and has significant differences to vanilla JavaScript.</p> + +<p>DSLs can't be read by the browser directly; they must be transformed into JavaScript or HTML first. <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview#Transformation">Transformation is an extra step in the development process</a>, but framework tooling generally includes the required tools to handle this step, or can be adjusted to include this step. While it is possible to build framework apps without using these domain-specific languages, embracing them will streamline your development process and make it easier to find help from the communities around those frameworks.</p> + +<h3 id="JSX">JSX</h3> + +<p><a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a>, which stands for JavaScript and XML, is an extension of JavaScript that brings HTML-like syntax to a JavaScript environment. It was invented by the React team for use in React applications, but can be used to develop other applications — like Vue apps, for instance.</p> + +<p>The following shows a simple JSX example:</p> + +<pre class="brush: js notranslate">const subject = "World"; +const header = ( + <header> + <h1>Hello, {subject}!</h1> + </header> +);</pre> + +<p>This expression represents an HTML <code><a href="/en-US/docs/Web/HTML/Element/header"><header></a></code> element with a <code><a href="/en-US/docs/Web/HTML/Element/Heading_Elements"><h1></a></code> element inside. The curly braces around <code>subject</code> on line 4 tell the application to read the value of the <code>subject</code> constant and insert it into our <code><h1></code>.</p> + +<p>When used with React, the JSX from the previous snippet would be compiled into this:</p> + +<pre class="brush: js notranslate">var subject = "World"; +var header = React.createElement("header", null, + React.createElement("h1", null, "Hello, ", subject, "!") +);</pre> + +<p>When ultimately rendered by the browser, the above snippet will produce HTML that looks like this:</p> + +<pre class="brush: html notranslate"><header> + <h1>Hello, World!</h1> +</header></pre> + +<h3 id="Handlebars">Handlebars</h3> + +<p>The <a href="https://handlebarsjs.com/">Handlebars</a> templating language is not specific to Ember applications, but it is heavily utilized in Ember apps. Handlebars code resembles HTML, but it has the option of pulling data in from elsewhere. This data can be used to influence the HTML that an application ultimately builds.</p> + +<p>Like JSX, Handlebars uses curly braces to inject the value of a variable. Handlebars uses a double-pair of curly braces, instead of a single pair.</p> + +<p>Given this Handlebars template:</p> + +<pre class="brush: html notranslate"><header> + <h1>Hello, \{{subject}}!</h1> +</header></pre> + +<p>And this data:</p> + +<pre class="brush: js notranslate">{ + subject: "World" +}</pre> + +<p>Handlebars will build HTML like this:</p> + +<pre class="brush: html notranslate"><header> + <h1>Hello, World!</h1> +</header></pre> + +<h3 id="TypeScript">TypeScript</h3> + +<p><a href="https://www.typescriptlang.org/">TypeScript</a> is a <em>superset</em> of JavaScript, meaning it extends JavaScript — all JavaScript code is valid TypeScript, but not the other way around. TypeScript is useful for the strictness it allows developers to enforce on their code. For instance, consider a function <code>add()</code>, which takes integers <code>a</code> and <code>b</code> and returns their sum.</p> + +<p>In JavaScript, that function could be written like this:</p> + +<pre class="brush: js notranslate">function add(a, b) { + return a + b; +}</pre> + +<p>This code might be trivial for someone accustomed to JavaScript, but it could still be clearer. JavaScript lets us use the <code>+</code> operator to concatenate strings together, so this function would technically still work if <code>a</code> and <code>b</code> were strings — it just might not give you the result you'd expect. What if we wanted to only allow numbers to be passed into this function? TypeScript makes that possible:</p> + +<pre class="brush: js notranslate">function add(a: number, b: number) { + return a + b; +}</pre> + +<p>The <code>: number</code> written after each parameter here tells TypeScript that both <code>a</code> and <code>b</code> must be numbers. If we were to use this function and pass <code>'2'</code> into it as an argument, TypeScript would raise an error during compilation, and we would be forced to fix our mistake. We could write our own JavaScript that raises these errors for us, but it would make our source code significantly more verbose. It probably makes more sense to let TypeScript handle such checks for us.</p> + +<h2 id="Writing_components">Writing components</h2> + +<p>As mentioned in the previous chapter, most frameworks have some kind of component model. React components can be written with JSX, Ember components with Handlebars, and Angular and Vue components with a templating syntax that lightly extends HTML.</p> + +<p>Regardless of their opinions on how components should be written, each framework's components offer a way to describe the external properties they may need, the internal state that the component should manage, and the events a user can trigger on the component's markup.</p> + +<p>The code snippets in the rest of this section will use React as an example, and are written with JSX.</p> + +<h3 id="Properties">Properties</h3> + +<p>Properties, or <strong>props</strong>, are external data that a component needs in order to render. Suppose you're building a website for an online magazine, and you need to be sure that each contributing writer gets credit for their work. You might create an <code>AuthorCredit</code> component to go with each article. This component needs to display a portrait of the author and a short byline about them. In order to know what image to render, and what byline to print, <code>AuthorCredit</code> needs to accept some props.</p> + +<p>A React representation of this <code>AuthorCredit</code> component might look something like this:</p> + +<pre class="brush: js notranslate">function AuthorCredit(props) { + return ( + <figure> + <img src={props.src} alt={props.alt} /> + <figcaption>{props.byline}</figcaption> + </figure> + ); +}</pre> + +<p><code>{props.src}</code>, <code>{props.alt}</code>, and <code>{props.byline}</code> represent where our props will be inserted into the component. To render this component, we would write code like this in the place where we want it rendered (which will probably be inside another component):</p> + +<pre class="brush: js notranslate"><AuthorCredit + src="./assets/zelda.png" + alt="Portrait of Zelda Schiff" + byline="Zelda Schiff is editor-in-chief of the Library Times." +/></pre> + +<p>This will ultimately render the following <code><a href="/en-US/docs/Web/HTML/Element/figure"><figure></a></code> element in the browser, with its structure as defined in the <code>AuthorCredit</code> component, and its content as defined in the props included on the <code>AuthorCredit</code> component call:</p> + +<pre class="brush: html notranslate"><figure> + <img + src="assets/zelda.png" + alt="Portrait of Zelda Schiff" + > + <figcaption> + Zelda Schiff is editor-in-chief of the Library Times. + </figcaption> +</figure></pre> + +<h3 id="State">State</h3> + +<p>We talked about the concept of <strong>state</strong> in the previous chapter — a robust state-handling mechanism is key to an effective framework, and each component may have data that needs its state controlled. This state will persist in some way as long as the component is in use. Like props, state can be used to affect how a component is rendered.</p> + +<p>As an example, consider a button that counts how many times it has been clicked. This component should be responsible for tracking its own <em>count</em> state, and could be written like this:</p> + +<pre class="brush: js notranslate">function CounterButton() { + const [count] = useState(0); + return ( + <button>Clicked {count} times</button> + ); +}</pre> + +<p><code><a href="https://reactjs.org/docs/hooks-reference.html#usestate">useState()</a></code> is a <strong><a href="https://reactjs.org/docs/hooks-intro.html">React hook</a></strong> which, given an initial data value, will keep track of that value as it is updated. The code will be initially rendered like so in the browser:</p> + +<pre class="brush: html notranslate"><button>Clicked 0 times</button></pre> + +<p>The <code>useState()</code> call keeps track of the <code>count</code> value in a robust way across the app, without you needing to write code to do that yourself.</p> + +<h3 id="Events">Events</h3> + +<p>In order to be interactive, components need ways to respond to browser events, so our applications can respond to our users. Frameworks each provide their own syntax for listening to browser events, which reference the names of the equivalent native browser events.</p> + +<p>In React, listening for the <code><a href="/en-US/docs/Web/API/Element/click_event">click</a></code> event requires a special property, <code>onClick</code>. Let’s update our <code>CounterButton</code> code from above to allow it to count clicks:</p> + +<pre class="brush: js notranslate">function CounterButton() { + const [count, setCount] = useState(0); + return ( + <button onClick={() => setCount(count + 1)}>Clicked {count} times</button> + ); +}</pre> + +<p>In this version we are using additional <code>useState()</code> functionality to create a special <code>setCount()</code> function, which we can invoke to update the value of <code>count</code>. We call this function on line 4, and set <code>count</code> to whatever its current value is, plus one.</p> + +<h2 id="Styling_components">Styling components</h2> + +<p>Each framework offers a way to define styles for your components — or for the application as a whole. Although each framework’s approach to defining the styles of a component is slightly different, all of them give you multiple ways to do so. With the addition of some helper modules, you can style your framework apps in <a href="https://sass-lang.com/">Sass</a> or <a href="http://lesscss.org/">Less</a>, or transpile your CSS stylesheets with <a href="https://postcss.org/">PostCSS</a>.</p> + +<h2 id="Handling_dependencies">Handling dependencies</h2> + +<p>All major frameworks provide mechanisms for handling dependencies — using components inside other components, sometimes with multiple hierarchy levels. As with other features, the exact mechanism will differ between frameworks, but the end result is the same. Components tend to import components into other components using the standard <a href="/en-US/docs/Web/JavaScript/Guide/Modules">JavaScript module syntax</a>, or at least something similar.</p> + +<h3 id="Components_in_components">Components in components</h3> + +<p>One key benefit of component-based UI architecture is that components can be composed together. Just like you can write HTML tags inside each other to build a website, you can use components inside other components to build a web application. Each framework allows you to write components that utilize (and thus depend on) other components.</p> + +<p>For example, our <code>AuthorCredit</code> React component might be utilized inside an <code>Article</code> component. That means that <code>Article</code> would need to import <code>AuthorCredit</code>.</p> + +<pre class="brush: js notranslate">import AuthorCredit from "./components/AuthorCredit";</pre> + +<p>Once that’s done, <code>AuthorCredit</code> could be used inside the <code>Article</code> component like this:</p> + +<pre class="brush: js notranslate"> ... + +<AuthorCredit /> + + ...</pre> + +<h3 id="Dependency_injection">Dependency injection</h3> + +<p>Real-world applications can often involve component structures with multiple levels of nesting. An <code>AuthorCredit</code> component nested many levels deep might, for some reason, need data from the very root level of our application.</p> + +<p>Let's say that the magazine site we're building is structured like this:</p> + +<pre class="brush: js notranslate"><App> + <Home> + <Article> + <AuthorCredit {/* props */} /> + </Article> + </Home> +</App></pre> + +<p>Our <code>App</code> component has data that our <code>AuthorCredit</code> component needs. We could rewrite <code>Home</code> and <code>Article</code> so that they know to pass props down, but this could get tedious if there are many, many levels between the origin and destination of our data. It's also excessive: <code>Home</code> and <code>Article</code> don’t actually make use of the author's portrait or byline, but if we want to get that information into the <code>AuthorCredit</code>, we will need to change <code>Home</code> and <code>Author</code> to accommodate it.</p> + +<p>The problem of passing data through many layers of components is called prop drilling, and it’s not ideal for large applications.</p> + +<p>To circumvent prop drilling, frameworks provide functionality known as dependency injection, which is a way to get certain data directly to the components that need it, without passing it through intervening levels. Each framework implements dependency injection under a different name, and in a different way, but the effect is ultimately the same.</p> + +<p>Angular calls this process <a href="https://angular.io/guide/dependency-injection">dependency injection</a>; Vue has <a href="https://vuejs.org/v2/api/#provide-inject"><code>provide()</code> and <code>inject()</code> component methods</a>; React has a <a href="https://reactjs.org/docs/context.html">Context API</a>; Ember shares state through <a href="https://guides.emberjs.com/release/services/">services</a>.</p> + +<h3 id="Lifecycle">Lifecycle</h3> + +<p>In the context of a framework, a component’s <strong>lifecycle</strong> is a collection of phases a component goes through from the time it is rendered by the browser (often called <em>mounting</em>) to the time that it is removed from the DOM (often called <em>unmounting</em>). Each framework names these lifecycle phases differently, and not all give developers access to the same phases. All of the frameworks follow the same general model: they allow developers to perform certain actions when the component <em>mounts</em>, when it <em>renders</em>, when it <em>unmounts</em>, and at many phases in between these.</p> + +<p>The <em>render</em> phase is the most crucial to understand, because it is repeated the most times as your user interacts with your application. It's run every time the browser needs to render something new, whether that new information is an addition to what's in the browser, a deletion, or an edit of what’s there.</p> + +<p>This <a href="http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/">diagram of a React component's lifecycle</a> offers a general overview of the concept.</p> + +<h2 id="Rendering_elements">Rendering elements</h2> + +<p>Just as with lifecycles, frameworks take different-but-similar approaches to how they render your applications. All of them track the current rendered version of your browser's DOM, and each makes slightly different decisions about how the DOM should change as components in your application re-render. Because frameworks make these decisions for you, you typically don't interact with the DOM yourself. This abstraction away from the DOM is more complex and more memory-intensive than updating the DOM yourself, but without it, frameworks could not allow you to program in the declarative way they’re known for.</p> + +<p>The <strong>Virtual DOM</strong> is an approach whereby information about your browser's DOM is stored in JavaScript memory. Your application updates this copy of the DOM, then compares it to the "real" DOM — the DOM that is actually rendered for your users — in order to decide what to render. The application builds a "diff" to compare the differences between the updated virtual DOM and the currently rendered DOM, and uses that diff to apply updates to the real DOM. Both React and Vue utilize a virtual DOM model, but they do not apply the exact same logic when diffing or rendering.</p> + +<p>You can <a href="https://reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom">read more about the Virtual DOM in the React docs</a>.</p> + +<p>The <strong>Incremental DOM</strong> is similar to the virtual DOM in that it builds a DOM diff to decide what to render, but different in that it doesn't create a complete copy of the DOM in JavaScript memory. It ignores the parts of the DOM that do not need to be changed. Angular is the only framework discussed so far in this module that uses an incremental DOM.</p> + +<p>You can <a href="https://auth0.com/blog/incremental-dom/">read more about the Incremental DOM on the Auth0 blog</a>.</p> + +<p>The <strong>Glimmer VM</strong> is unique to Ember. It is not a virtual DOM nor an incremental DOM; it is a separate process through which Ember's templates are transpiled into a kind of "byte code" that is easier and faster to read than JavaScript.</p> + +<h2 id="Routing">Routing</h2> + +<p>As <a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction#Routing">mentioned in the previous chapter, routing</a> is an important part of the web experience. To avoid a broken experience in sufficiently complex apps with lots of views, each of the frameworks covered in this module provides a library (or more than one library) that helps developers implement client-side routing in their applications.</p> + +<h2 id="Testing">Testing</h2> + +<p>All applications benefit from test coverage that ensures your software continues to behave in the way that you'd expect, and web applications are no different. Each framework's ecosystem provides tooling that facilitates the writing of tests. Testing tools are not built into the frameworks themselves, but the command-line interface tools used to generate framework apps give you access to the appropriate testing tools.</p> + +<p>Each framework has extensive tools in its ecosystem, with capabilities for unit and integration testing alike.</p> + +<p><a href="https://testing-library.com/">Testing Library</a> is a suite of testing utilities that has tools for many JavaScript environments, including React, Vue, and Angular. The Ember docs cover the <a href="https://guides.emberjs.com/release/testing/">testing of Ember apps</a>.</p> + +<p>Here’s a quick test for our <code>CounterButton</code> written with the help of React Testing Library — it tests a number of things, such as the button's existence, and whether the button is displaying the correct text after being clicked 0, 1, and 2 times:</p> + +<pre class="brush: js notranslate">import React from "react"; +import { render, fireEvent } from "@testing-library/react"; +import "@testing-library/jest-dom/extend-expect"; + +import CounterButton from "./CounterButton"; + +it("renders a semantic with an initial state of 0", () => { + const { getByRole } = render(<CounterButton />); + const btn = getByRole("button"); + + expect(btn).toBeInTheDocument(); + expect(btn).toHaveTextContent("Clicked 0 times"); +}); + +it("Increments the count when clicked", () => { + const { getByRole } = render(<CounterButton />); + const btn = getByRole("button"); + + fireEvent.click(btn); + expect(btn).toHaveTextContent("Clicked 1 times"); + + fireEvent.click(btn); + expect(btn).toHaveTextContent("Clicked 2 times"); +});</pre> + +<h2 id="Summary">Summary</h2> + +<p>At this point you should have more of an idea about the actual languages, features, and tools you'll be using as you create applications with frameworks. I'm sure you’re enthusiastic to get going and actually do some coding, and that's what you are going to do next! At this point you can choose which framework you'd like to start learning first:</p> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Vue</a></li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: We only have three framework tutorial series available now, but we hope to have more available in the future.</p> +</div> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_components/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_components/index.html new file mode 100644 index 0000000000..39ef145118 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_components/index.html @@ -0,0 +1,450 @@ +--- +title: Componentizingapp +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">At this point, our app is a monolith. Before we can make it do things, we need to break it apart into manageable, descriptive components. React doesn’t have any hard rules for what is and isn’t a component – that’s up to you! In this article we will show you a sensible way to break our app up into components.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To show a sensible way of breaking our todo list app into components.</td> + </tr> + </tbody> +</table> + +<h2 id="Defining_our_first_component">Defining our first component</h2> + +<p>Defining a component can seem tricky until you have some practice, but the gist is:</p> + +<ul> + <li>If it represents an obvious "chunk" of your app, it's probably a component</li> + <li>If it gets reused often, it's probably a component.</li> +</ul> + +<p>That second bullet is especially valuable: making a component out of common UI elements allows you to change your code in one place and see those changes everywhere that component is used. You don't have to break everything out into components right away, either. Let's take the second bullet point as inspiration and make a component out of the most reused, most important piece of the UI: a todo list item.</p> + +<h2 id="Make_a_<Todo_>">Make a <code><Todo /></code></h2> + +<p>Before we can make a component, we should create a new file for it. In fact, we should make a directory just for our components. The following commands make a <code>components</code> directory and then, within that, a file called <code>Todo.js</code>. Make sure you're in the root of your app before you run these!</p> + +<pre class="brush: bash notranslate">mkdir src/components +touch src/components/Todo.js</pre> + +<p>Our new <code>Todo.js</code> file is currently empty! Open it up and give it its first line:</p> + +<pre class="brush: js notranslate">import React from "react";</pre> + +<p>Since we're going to make a component called <code>Todo</code>, you can start adding the code for that to <code>Todo.js</code> too, as follows. In this code, we define the function and export it on the same line:</p> + +<pre class="brush: js notranslate">export default function Todo() { + return ( + + ); +}</pre> + +<p>This is OK so far, but our component has to return something! Go back to <code>src/App.js</code>, copy the first <code><a href="/en-US/docs/Web/HTML/Element/li"><li></a></code> from inside the unordered list, and paste it into <code>Todo.js</code> so that it reads like this:</p> + +<pre class="brush: js notranslate">export default function Todo() { + return ( + <li className="todo stack-small"> + <div className="c-cb"> + <input id="todo-0" type="checkbox" defaultChecked={true} /> + <label className="todo-label" htmlFor="todo-0"> + Eat + </label> + </div> + <div className="btn-group"> + <button type="button" className="btn"> + Edit <span className="visually-hidden">Eat</span> + </button> + <button type="button" className="btn btn__danger"> + Delete <span className="visually-hidden">Eat</span> + </button> + </div> + </li> + ); +}</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Components must always return something. If at any point in the future you try to render a component that does not return anything, React will display an error in your browser.</p> +</div> + +<p>Our <code>Todo</code> component is complete, at least for now; now we can use it. In <code>App.js</code>, add the following line near the top of the file to import <code>Todo</code>:</p> + +<pre class="brush: js notranslate">import Todo from "./components/Todo";</pre> + +<p>With this component imported, you can replace all of the <code><li></code> elements in <code>App.js</code> with <code><Todo /></code> component calls. Your <code><ul></code> should read like this:</p> + +<pre class="brush: js notranslate"><ul + role="list" + className="todo-list stack-large stack-exception" + aria-labelledby="list-heading" +> + <Todo /> + <Todo /> + <Todo /> +</ul></pre> + +<p>When you look back at your browser, you'll notice something unfortunate: your list now repeats the first task three times!</p> + +<p><img alt="Our todo list app, with todo components repeating because the label is hardcoded into the component" src="https://mdn.mozillademos.org/files/17255/todo-list-repeating-todos.png" style="border-style: solid; border-width: 1px; height: 954px; width: 856px;"></p> + +<p>We don't only want to eat; we have other things to — well — to do. Next we'll look at how we can make different component calls render unique content.</p> + +<h2 id="Make_a_unique_<Todo_>">Make a <em>unique</em> <code><Todo /></code></h2> + +<p>Components are powerful because they let us re-use pieces of our UI, and refer to one place for the source of that UI. The problem is, we don't typically want to reuse all of each component; we want to reuse most parts, and change small pieces. This is where props come in.</p> + +<h3 id="Whats_in_a_name">What's in a <code>name</code>?</h3> + +<p>In order to track the names of tasks we want to complete, we should ensure that each <code><Todo /></code> component renders a unique name.</p> + +<p>In <code>App.js</code>, give each <code><Todo /></code> a name prop. Let’s use the names of our tasks that we had before:</p> + +<pre class="brush: js notranslate"><Todo name="Eat" /> +<Todo name="Sleep" /> +<Todo name="Repeat" /></pre> + +<p>When your browser refreshes, you will see… the exact same thing as before. We gave our <code><Todo /></code> some props, but we aren't using them yet. Let's go back to <code>Todo.js</code> and remedy that.</p> + +<p>First modify your <code>Todo()</code> function definition so that it takes <code>props</code> as a parameter. You can <code>console.log()</code> your <code>props</code> as we did before, if you'd like to check that they are being received by the component correctly.</p> + +<p>Once you're confident that your component is getting its <code>props</code>, you can replace every occurrence of <code>Eat</code> with your <code>name</code> prop. Remember: when you're in the middle of a JSX expression, you use curly braces to inject the value of a variable.</p> + +<p>Putting all that together, your <code>Todo()</code> function should read like this:</p> + +<pre class="brush: js notranslate">export default function Todo(props) { + return ( + <li className="todo stack-small"> + <div className="c-cb"> + <input id="todo-0" type="checkbox" defaultChecked={true} /> + <label className="todo-label" htmlFor="todo-0"> + {props.name} + </label> + </div> + <div className="btn-group"> + <button type="button" className="btn"> + Edit <span className="visually-hidden">{props.name}</span> + </button> + <button type="button" className="btn btn__danger"> + Delete <span className="visually-hidden">{props.name}</span> + </button> + </div> + </li> + ); +}</pre> + +<p><em>Now</em> your browser should show three unique tasks. Another problem remains though: they're all still checked by default.</p> + +<p><img alt="Our todo list, with different todo labels now they are passed into the components as props" src="https://mdn.mozillademos.org/files/17256/todo-list-unique-todos.png" style="border-style: solid; border-width: 1px; height: 954px; width: 856px;"></p> + +<h3 id="Is_it_completed">Is it <code>completed</code>?</h3> + +<p>In our original static list, only <code>Eat</code> was checked. Once again, we want to reuse <em>most</em> of the UI that makes up a <code><Todo /></code> component, but change one thing. That's a good job for another prop! Give each <code><Todo /></code> call in <code>App.js</code> a new prop of <code>completed</code>. The first (<code>Eat</code>) should have a value of <code>true</code>; the rest should be <code>false</code>:</p> + +<pre class="brush: js notranslate"><Todo name="Eat" completed={true} /> +<Todo name="Sleep" completed={false} /> +<Todo name="Repeat" completed={false} /></pre> + +<p>As before, we must go back to <code>Todo.js</code> to actually use these props. Change the <code>defaultChecked</code> attribute on the <code><input /></code> so that its value is equal to the <code>completed</code> prop. Once you’re done, the Todo component's <code><input /></code> element will read like this:</p> + +<pre class="brush: js notranslate"><input id="todo-0" type="checkbox" defaultChecked={props.completed} /></pre> + +<p>And your browser should update to show only <code>Eat</code> being checked:</p> + +<p><img alt="Our todo list app, now with differing checked states - some checkboxes are checked, others not" src="https://mdn.mozillademos.org/files/17254/todo-list-differing-checked-states.png" style="border-style: solid; border-width: 1px; height: 954px; width: 856px;"></p> + +<p>If you change each <code><Todo /></code> component’s <code>completed</code> prop, your browser will check or uncheck the equivalent rendered checkboxes accordingly.</p> + +<h3 id="Gimme_some_id_please">Gimme some <code>id</code>, please</h3> + +<p>Right now, our <code><Todo /></code> component gives every task an <code>id</code> attribute of <code>todo-0</code>. This is bad HTML because <a href="/en-US/docs/Web/HTML/Global_attributes/id"><code>id</code> attributes</a> must be unique (they are used as unique identifiers for document fragments, by CSS, JavaScript, etc.). This means we should give our component an <code>id</code> prop that takes a unique value for each <code>Todo</code>.</p> + +<p>To follow the same pattern we had initially, let's give each instance of the <code><Todo /></code> component an ID in the format of <code>todo-i</code>, where <code>i</code> gets larger by one every time:</p> + +<pre class="brush: js notranslate"><Todo name="Eat" completed={true} id="todo-0" /> +<Todo name="Sleep" completed={false} id="todo-1" /> +<Todo name="Repeat" completed={false} id="todo-2" /></pre> + +<p>Now go back to <code>Todo.js</code> and make use of the <code>id</code> prop. It needs to replace the value of the <code>id</code> attribute of the <code><input /></code> element, as well as the value of its label's <code>htmlFor</code> attribute:</p> + +<pre class="brush: js notranslate"><div className="c-cb"> + <input id={props.id} type="checkbox" defaultChecked={props.completed} /> + <label className="todo-label" htmlFor={props.id}> + {props.name} + </label> +</div></pre> + +<h2 id="So_far_so_good">So far, so good?</h2> + +<p>We’re making good use of React so far, but we could do better! Our code is repetitive. The three lines that render our <code><Todo /></code> component are almost identical, with only one difference: the value of each prop.</p> + +<p>We can clean up our code with one of JavaScript's core abilities: iteration. To use iteration, we should first re-think our tasks.</p> + +<h2 id="Tasks_as_data">Tasks as data</h2> + +<p>Each of our tasks currently contains three pieces of information: its name, whether it has been checked, and its unique ID. This data translates nicely to an object. Since we have more than one task, an array of objects would work well in representing this data.</p> + +<p>In <code>src/index.js</code>, make a new <code>const</code> beneath the final import, but above <code>ReactDOM.render()</code>:</p> + +<pre class="brush: js notranslate">const DATA = [ + { id: "todo-0", name: "Eat", completed: true }, + { id: "todo-1", name: "Sleep", completed: false }, + { id: "todo-2", name: "Repeat", completed: false } +];</pre> + +<p>Next, we'll pass <code>DATA</code> to <code><App /></code> as a prop, called <code>tasks</code>. The final line of <code>src/index.js</code> should read like this:</p> + +<pre class="brush: js notranslate">ReactDOM.render(<App tasks={DATA} />, document.getElementById("root"));</pre> + +<p>This array is now available to the App component as <code>props.tasks</code>. You can <code>console.log()</code> it to check, if you’d like.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: <code>ALL_CAPS</code> constant names have no special meaning in JavaScript; they’re a convention that tells other developers "this data will never change after being defined here”.</p> +</div> + +<h2 id="Rendering_with_iteration">Rendering with iteration</h2> + +<p>To render our array of objects, we have to turn each one into a <code><Todo /></code> component. JavaScript gives us an array method for transforming data into something else: <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map">Array.prototype.map()</a></code>.</p> + +<p>Above the return statement of <code>App()</code>, make a new <code>const</code> called <code>taskList</code> and use <code>map()</code> to transform it. Let's start by turning our <code>tasks</code> array into something simple: the <code>name</code> of each task:</p> + +<pre class="brush: js notranslate">const taskList = props.tasks.map(task => task.name);</pre> + +<p>Let’s try replacing all the children of the <code><ul></code> with <code>taskList</code>:</p> + +<pre class="brush: js notranslate"><ul + role="list" + className="todo-list stack-large stack-exception" + aria-labelledby="list-heading" +> + {taskList} +</ul></pre> + +<p>This gets us some of the way towards showing all the components again, but we’ve got more work to do: the browser currently renders each task's name as unstructured text. We’re missing our HTML structure — the <code><li></code> and its checkboxes and buttons!</p> + +<p><img alt="Our todo list app with the todo item labels just shown bunched up on one line" src="https://mdn.mozillademos.org/files/17257/todo-list-unstructured-names.png" style="border-style: solid; border-width: 1px; height: 627px; width: 856px;"></p> + +<p>To fix this, we need to return a <code><Todo /></code> component from our <code>map()</code> function — remember that JSX allows us to mix up JavaScript and markup structures! Let's try the following instead of what we have already:</p> + +<pre class="brush: js notranslate"> const taskList = props.tasks.map(task => <Todo />);</pre> + +<p>Look again at your app; now our tasks look more like they used to, but they’re missing the names of the tasks themselves. Remember that each task we map over has the <code>id</code>, <code>name</code>, and <code>checked</code> properties we want to pass into our <code><Todo /></code> component. If we put that knowledge together, we get code like this:</p> + +<pre class="brush: js notranslate">const taskList = props.tasks.map(task => ( + <Todo id={task.id} name={task.name} completed={task.completed} /> +));</pre> + +<p>Now the app looks like it did before, and our code is less repetitive.</p> + +<h2 id="Unique_keys">Unique keys</h2> + +<p>Now that React is rendering our tasks out of an array, it has to keep track of which one is which in order to render them properly. React tries to do its own guesswork to keep track of things, but we can help it out by passing a <code>key</code> prop to our <code><Todo /></code> components. <code>key</code> is a special prop that's managed by React – you cannot use the word <code>key</code> for any other purpose.</p> + +<p>Because keys should be unique, we're going to re-use the <code>id</code> of each task object as its key. Update your <code>taskList</code> constant like so:</p> + +<pre class="brush: js notranslate">const taskList = props.tasks.map(task => ( + <Todo + id={task.id} + name={task.name} + completed={task.completed} + key={task.id} + /> + ) +);</pre> + +<p><strong>You should always pass a unique key to anything you render with iteration.</strong> Nothing obvious will change in your browser, but if you do not use unique keys, React will log warnings to your console and your app may behave strangely!</p> + +<h2 id="Componentizing_the_rest_of_the_app">Componentizing the rest of the app</h2> + +<p>Now that we've got our most important component sorted out, we can turn the rest of our app into components. Remembering that components are either obvious pieces of UI, or reused pieces of UI, or both, we can make two more components:</p> + +<ul> + <li><code><Form/></code></li> + <li><code><FilterButton/></code></li> +</ul> + +<p>Since we know we need both, we can batch some of the file creation work together with a terminal command. Run this command in your terminal, taking care that you're in the root directory of your app:</p> + +<pre class="brush: bash notranslate">touch src/components/Form.js src/components/FilterButton.js</pre> + +<h3 id="The_<Form_>">The <code><Form /></code></h3> + +<p>Open <code>components/Form.js</code> and do the following:</p> + +<ul> + <li>Import <code>React</code> at the top of the file, like we did in <code>Todo.js</code>.</li> + <li>Make yourself a new <code>Form()</code> component with the same basic structure as <code>Todo()</code>, and export that component.</li> + <li>Copy the <code><form></code> tags and everything between them from inside <code>App.js</code>, and paste them inside <code>Form()</code>’s <code>return</code> statement.</li> + <li>Export <code>Form</code> at the end of the file.</li> +</ul> + +<p>Your <code>Form.js</code> file should read like this:</p> + +<pre class="brush: js notranslate">import React from "react"; + +function Form(props) { + return ( + <form> + <h2 className="label-wrapper"> + <label htmlFor="new-todo-input" className="label__lg"> + What needs to be done? + </label> + </h2> + <input + type="text" + id="new-todo-input" + className="input input__lg" + name="text" + autoComplete="off" + /> + <button type="submit" className="btn btn__primary btn__lg"> + Add + </button> + </form> + ); +} + +export default Form;</pre> + +<h3 id="The_<FilterButton_>">The <FilterButton /></h3> + +<p>Do the same things you did to create <code>Form.js</code> inside <code>FilterButton.js</code>, but call the component <code>FilterButton()</code> and copy the HTML for the first button inside the <code><div></code> element with the <code>class</code> of <code>filters</code> from <code>App.js</code> into the <code>return</code> statement.</p> + +<p>The file should read like this:</p> + +<pre class="brush: js notranslate">import React from "react"; + +function FilterButton(props) { + return ( + <button type="button" className="btn toggle-btn" aria-pressed="true"> + <span className="visually-hidden">Show </span> + <span>all </span> + <span className="visually-hidden"> tasks</span> + </button> + ); +} + +export default FilterButton;</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: You might notice that we are making the same mistake here as we first made for the <code><Todo /></code> component, in that each button will be the same. That’s fine! We’re going to fix up this component later on, in <a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering#Back_to_the_filter_buttons">Back to the filter buttons</a>.</p> +</div> + +<h2 id="Importing_all_our_components">Importing all our components</h2> + +<p>Let's make use of our new components.</p> + +<p>Add some more <code>import</code> statements to the top of <code>App.js</code>, to import them.</p> + +<p>Then, update the <code>return</code> statement of <code>App()</code> so that it renders our components. When you’re done, <code>App.js</code> will read like this:</p> + +<pre class="brush: js notranslate">import React from "react"; +import Form from "./components/Form"; +import FilterButton from "./components/FilterButton"; +import Todo from "./components/Todo"; + +function App(props) { + const taskList = props.tasks.map(task => ( + <Todo + id={task.id} + name={task.name} + completed={task.completed} + key={task.id} + /> + ) + ); + return ( + <div className="todoapp stack-large"> + <Form /> + <div className="filters btn-group stack-exception"> + <FilterButton /> + <FilterButton /> + <FilterButton /> + </div> + <h2 id="list-heading">3 tasks remaining</h2> + <ul + role="list" + className="todo-list stack-large stack-exception" + aria-labelledby="list-heading" + > + {taskList} + </ul> + </div> + ); +} + +export default App;</pre> + +<p>With this in place, we’re <em>almost</em> ready to tackle some interactivity in our React app!</p> + +<h2 id="Summary">Summary</h2> + +<p>And that's it for this article — we've gone into some depth on how to break up your app nicely into components, end render them efficiently. Now we'll go on to look at how we handle events in React, and start adding some interactivity.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_getting_started/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_getting_started/index.html new file mode 100644 index 0000000000..8ee4d5d755 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/react_getting_started/index.html @@ -0,0 +1,484 @@ +--- +title: React 入门 +slug: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started +tags: + - JavaScript + - React + - 初学者 + - 学习 + - 客户端 + - 框架 +translation_of: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary"><font><font>本文会引导我们进入一段 React 学习之旅。</font><font>我们将逐步了解有关它的背景和用例的一些细节,在自己的电脑上建起基本的 React 工具链,创建并使用一个简单的入门应用程序,以学习一些关于 React 在此过程中如何工作的知识。</font></font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"><font><font>先决条件:</font></font></th> + <td> + <p><font><font>熟悉核心 </font></font><a href="/en-US/docs/Learn/HTML"><font><font>HTML</font></font></a><font><font>,</font></font><font><font><a href="/en-US/docs/Learn/CSS">CSS</a> </font></font><font><font>和 </font></font><font><font><a href="/en-US/docs/Learn/JavaScript">JavaScript</a> </font></font><font><font>语言,了解</font></font><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line"><font><font>终端/命令行</font></font></a><font><font>。</font></font></p> + + <p><font><font>React 使用称为 JSX(JavaScript 和 XML)的 HTML-in-JavaScript 语法。</font><font>熟悉 HTML 和 JavaScript 可以帮助您学习 JSX,并更好地确定应用程序中的错误是与 JavaScript 还是与 React 的更特定领域相关。</font></font></p> + </td> + </tr> + <tr> + <th scope="row"><font><font>目的:</font></font></th> + <td><font><font>要设置本地 React 开发环境,创建一个启动应用程序,并了解其工作原理</font></font></td> + </tr> + </tbody> +</table> + +<h2 id="你好_React"><font><font>你好 React</font></font></h2> + +<p><font><font>如其官方口号所示,</font></font><font><font><a href="https://reactjs.org/">React</a> 是一个用于构建用户界面的库。React 不是一个框架 —— 它的应用甚至不局限于 Web 开发,它可以与其他库一起使用以渲染到特定环境。例如,</font></font><font><font><a href="https://reactnative.dev/">React Native</a> </font></font><font><font>可用于构建移动应用程序;</font></font><font><font><a href="https://facebook.github.io/react-360/">React 360</a> </font></font><font><font>可用于构建虚拟现实应用程序……</font></font></p> + +<p><font><font>为了构建 Web 应用,开发人员将 React 与 </font></font><a href="https://reactjs.org/docs/react-dom.html"><font><font>ReactDOM 结合使用</font></font></a><font><font>。</font><font>React 和 ReactDOM 通常被与其他真正的 Web 开发框架相提并论,并用于解决相同的问题。</font><font>当我们将 React 称为“框架”时,</font></font>就是在进行口语化的理解<font><font>。</font></font></p> + +<p><font><font>React 的主要目标是最大程度地减少开发人员构建 UI 时发生的错误。它通过使用组件——描述部分用户界面的、自包含的逻辑代码段——来实现此目的。这些组件可以组合在一起以创建完整的 UI,React 将许多渲染工作进行抽象化,使您可以专注于 UI 设计(译者注:显而易见,此设计不等于视觉稿的设计)。</font></font></p> + +<h2 id="用例"><font><font>用例</font></font></h2> + +<p><font><font>与本模块中涵盖的其他框架不同,React 不会对代码约定或文件组织实施严格的规则。</font><font>这使团队可以设置最适合自己的约定,并以他们希望的任何方式采用 React。</font><font>React 可以处理一个按钮,一个界面的几个部分或应用程序的整个用户界面。</font></font></p> + +<div style="padding-top: 5px;"> +<p><span>尽管 React 可以用于<a href="https://reactjs.org/docs/add-react-to-a-website.html">界面的小片段</a>中,但要和 jQuery 这样的库,甚至是像 Vue 这样的框架那样“引入”应用程序并不容易 —— 当你使用 React 构建整个应用程序时更容易上手</span><span>。</span></p> +</div> + +<p>另外,许多开发人员的经验对于 React 应用程序也是有用处的,例如使用 JSX 编写界面是需要编译过程的。在网站上添加类似于 Babel 的编译器会让网站上代码的运行速度变慢,因此开发人员通常会在构建项目的时候设置这样的工具。React 对于工具的要求可以说是很高的,但这是能够学习解决的。</p> + +<p>本文将重点介绍使用 React 通过 Facebook 的 <a href="https://create-react-app.dev/">create-react-app</a> 内的工具渲染应用程序中整个用户界面的用例。</p> + +<h2 id="React_如何使用_JavaScript">React 如何使用 JavaScript?</h2> + +<p>React 中的许多模式都使用了现代 JavaScript 的功能。React 与 JavaScript 的最大区别在于 <a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a> 语法的使用上。JSX 是在 JavaScript 语法上的拓展,因此类似于 HTML 的代码可以和 JSX 共存。例如:</p> + +<pre class="brush: js notranslate">const heading = <h1>Mozilla Developer Network</h1>;</pre> + +<p>该 heading 常量称为 <strong>JSX 表达式</strong>。React 可以使用它在我们的应用程序中渲染 <code><a href="/en-US/docs/Web/HTML/Element/Heading_Elements"><h1></a></code> 标签。</p> + +<p>假设出于语义原因,我们想将 heading 包装 <code><a href="/en-US/docs/Web/HTML/Element/header"><header></a></code> 在标记中?JSX 方法允许我们将元素彼此嵌套,就像使用 HTML 一样:</p> + +<pre class="brush: js notranslate">const header = ( + <header> + <h1>Mozilla Developer Network</h1> + </header> +);</pre> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:上一个代码段中的括号并非 JSX 的一部分,它对您的应用程序没有任何影响,括号只是用来向您(和您的计算机)表明其中的多行代码属于同一个表达式(译者注:原文表述实在有点啰嗦)。因此上面的代码等同于:</p> + +<pre class="brush: js notranslate">const header = <header> + <h1>Mozilla Developer Network</h1> +</header></pre> + +<p>这看起来多少有点不适感,因为表达式前面的 <code><a href="/en-US/docs/Web/HTML/Element/header"><header></a></code> 标记没有缩进与其对应的结束标记相同的位置。</p> +</div> + +<p>浏览器是无法读取直接解析 JSX 的。我们的 header 表达式经过( <a href="https://babeljs.io/">Babel</a> 或 <a href="https://parceljs.org/">Parcel</a> 之类的工具)编译之后是这样的:</p> + +<pre class="brush: js notranslate">const header = React.createElement("header", null, + React.createElement("h1", null, "Mozilla Developer Network") +);</pre> + +<p><em>可以</em>跳过编译步骤,并使用 <code><a href="https://reactjs.org/docs/react-api.html#createelement">React.createElement()</a></code> 自己编写 UI。但是,这样做会失去 JSX 的声明性优势,并且代码变得更难以阅读。编译是开发过程中的一个额外步骤,但是 React 社区中的许多开发人员都认为 JSX 的可读性值得。另外,流行的工具使 JSX-to-JavaScript 编译成为其设置过程的一部分。除非您愿意,否则不必自己配置编译。</p> + +<p>由于 JSX 是 HTML 和 JavaScript 的结合,因此一些开发人员认为它很直观。其他人则说它的混合特性使它变得混乱。但是,一旦熟悉了它,它将使您能够更快,更直观地构建用户界面,并使其他人一眼就能更好地理解您的代码库。</p> + +<p>要阅读有关 JSX 的更多信息,请查看 React 团队的 <a href="https://reactjs.org/docs/jsx-in-depth.html">JSX In Depth</a> 文章。</p> + +<h2 id="设置您的第一个_React_应用">设置您的第一个 React 应用</h2> + +<p>有很多使用 React 的方法,但是我们将使用命令行界面(CLI)工具 create-react-app,如前所述,该方法通过安装一些软件包并创建一些软件包来加快开发 React 应用程序的过程。文件供您处理上述工具。</p> + +<p>通过将一些 <code><a href="/en-US/docs/Web/HTML/Element/script"><script></a></code> 元素复制到 HTML 文件中,可以在<a href="https://reactjs.org/docs/add-react-to-a-website.html">没有 create-react-app 的情况下将 React 添加到网站</a>,但是 create-react-app CLI 是 React 应用程序的常见起点。使用它可以让您花费更多的时间来构建应用,而花更少的时间进行设置。</p> + +<h3 id="要求">要求</h3> + +<p>为了使用 create-react-app,您需要安装 <a href="https://nodejs.org/en/">Node.js</a>。建议您使用长期支持(LTS)版本。Node 包括 npm(Node 程序包管理器)和 npx(Node 程序包运行器)</p> + +<p>您也可以使用 Yarn 软件包管理器作为替代方案,但是我们假设在这套教程中使用 npm。有关 npm 和 yarn 的更多信息,请参见<a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">程序包管理基础</a>。</p> + +<p>如果您使用的是 Windows,则需要安装一些软件以与 Unix/macOS 终端保持同等地位,才能使用本教程中提到的终端命令。<strong>Gitbash</strong>(作为 <a href="https://gitforwindows.org/">git Windows 工具集</a>的一部分提供)或<strong><a href="https://docs.microsoft.com/en-us/windows/wsl/about">适用于 Linux 的 Windows 子系统</a></strong>(<strong>WSL</strong>)均适用。有关这些以及一般终端命令的更多信息,请参见<a href="/zh-CN/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">命令行速成课程</a>。</p> + +<p>还请记住,React 和 ReactDOM 生成的应用程序只能在相当现代的一组浏览器上运行 —— 通过某些 polyfill 可以使用 IE9+。在阅读这些教程时,建议您使用 Firefox,Safari 或 Chrome 等现代浏览器。</p> + +<p>另外,有关更多信息,请参见以下内容:</p> + +<ul> + <li><a href="https://nodejs.org/en/knowledge/getting-started/npm/what-is-npm/">"What is npm" on nodejs.org</a></li> + <li><a href="https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner">"Introducing npx" on the npm blog</a></li> + <li><a href="https://create-react-app.dev/">The create-react-app documentation</a></li> +</ul> + +<h3 id="初始化您的应用">初始化您的应用</h3> + +<p><code>create-react-app</code> ,该命令接受一个参数:即你想给自己的应用所起的名字。<code>create-react-app</code> 将为此应用创建一个同名的文件夹,并在其中创建所需文件。在你打算放置你的应用程序的文件夹下打开你的命令终端,并键入命令:</p> + +<pre class="brush: bash notranslate">npx create-react-app moz-todo-react</pre> + +<p>这句命令创建了一个名为 <code>moz-todo-react</code> 的文件夹, 并在此文件夹里做了如下工作:</p> + +<ul> + <li>为你的应用程序安装了一些 npm 包;</li> + <li>写入 react 应用启动所需要的脚本文件;</li> + <li>创建一系列结构化的子文件夹和文件,奠定应用程序的基础架构;</li> + <li>如果你的电脑上安装了 git 的话,顺便帮你把 git 仓库也建好。</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>注意:如果你的电脑上安装了 yarn 的话, create-react-app 会默认使用 yarn 而非 npm。如果你同时安装了 yarn 和 npm ,但你希望使用 npm 的话,在 create-react-app 的时候需要输入 </strong> <code>--use-npm</code><strong> :</strong></p> + +<pre class="brush: bash notranslate">npx create-react-app moz-todo-react --use-npm</pre> +</div> + +<p><code>create-react-app</code> 运行的时候会在终端上显示一些与其状态相关的信息,通常情况下无需为此担心。运行需要一点时间,在此期间你可以适度放松一下。</p> + +<p>处理完成之后,你可以 <code>cd</code> 到 <code>moz-todo-react</code> 文件夹下,然后键入 <code>npm start</code> 命令并回车,先前由 create-react-app 创建的脚本会启动一个地服务 <a href="localhost:3000">localhost:3000</a>,并打开你的默认浏览器来访问这个服务。成功启动浏览器的话,你的浏览器上会显示如下画面:</p> + +<p><img alt="Firefox MacOS的屏幕截图,打开到localhost:3000,显示了默认的create-react-app应用程序" src="https://mdn.mozillademos.org/files/17203/default-create-react-app.png" style="border-style: solid; border-width: 1px; height: 980px; width: 1600px;"></p> + +<h3 id="应用结构">应用结构</h3> + +<p>create-react-app 提供了开发React应用所需的工具。它的初始文件结构如下:</p> + +<pre class="notranslate">moz-todo-react +├── README.md +├── node_modules +├── package.json +├── package-lock.json +├── .gitignore +├── public +│ ├── favicon.ico +│ ├── index.html +│ └── manifest.json +└── src + ├── App.css + ├── App.js + ├── App.test.js + ├── index.css + ├── index.js + ├── logo.svg + └── serviceWorker.js</pre> + +<p>目录 <strong><code>src</code></strong> 是我们花费时间最多的地方,因为它是我们React应用源码存放的目录。</p> + +<p>目录 <strong><code>public</code></strong> 包含了开发应用时浏览器会读取的文件,其中最重要的就是 <code>index.html</code>。React将目录 <strong><code>src</code></strong> 中的代码嵌入这个文件,从而浏览器才能运行此文件。 <code>index.html</code>中的有些内容关乎create-react-app的运作,因此除非你知道自己在做什么样的修改,否则不建议编辑这个文件。当然,你可以修改<code>index.html</code>中的 <code><a href="/en-US/docs/Web/HTML/Element/title"><title></a></code> 元素的内容来表现此应用程序通俗易懂的名称。</p> + +<p>目录 <code>public</code> 会在建立并部署此应用的时候更新。此教程不涉及部署,你可以参考 <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">Deploying our app</a> 这一篇教程。</p> + +<p>文件 <code>package.json</code> 包含了Node.js/npm为了建立该应用程序所管理着的文件信息。这个文件不是React应用独有的。你无需理解这个文件也能看懂这篇教程。 不过,如果你想了解更多,你可以阅读 <a href="https://nodejs.org/en/knowledge/getting-started/npm/what-is-the-file-package-json/">What is the file `package.json`? on NodeJS.org</a> 和 <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">Package management basics</a>。</p> + +<h2 id="探索第一个_React_组件_—_<App>">探索第一个 React 组件 — <code><App/></code></h2> + +<p>在React中,组件是组成应用程序的可重复利用的模块。组件可大可小,但它们都只有单一的、明确的功能。</p> + +<p>打开 <code>src/App.js</code>,之前打开的页面也提示我们对这个文件进行编辑。这个文件包含了我们第一个组件 <code>App</code>,内容如下:</p> + +<pre class="brush: js notranslate">import React from 'react'; +import logo from './logo.svg'; +import './App.css'; + +function App() { + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Edit <code>src/App.js</code> and save to reload. + </p> + <a + className="App-link" + href="https://reactjs.org" + target="_blank" + rel="noopener noreferrer" + > + Learn React + </a> + </header> + </div> + ); +} +export default App;</pre> + +<p>文件 <code>App.js</code> 主要由三部分组成: 顶部的 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/import">import</a></code> 语句, 中间的 <code>App</code> 组件,以及底部的 <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/export">export</a></code> 语句。大多数React组件都遵循这个模式。</p> + +<h3 id="import_语句">import 语句</h3> + +<p>脚本开头的 <code>import</code> 语句允许在此脚本中使用其他文件中的代码,让我们更进一步地了解这些语句。</p> + +<pre class="brush: js notranslate">import React from 'react'; +import logo from './logo.svg'; +import './App.css';</pre> + +<p>第一句代码引入了 React 库,这是为了将代码中的 JSX 语句转为<code>React.createElement()</code>,所有的 React 模块都应该引入 React 模块,否则会抛错。</p> + +<p>第二句代码引入了 <code>.'/logo.svg'</code>。注意文件路径以 <code>./</code> 开头、由 <code>.svg</code> 尾——表明这是一个本地文件,并且它不是 JavaScript 文件。</p> + +<div class="blockIndicator note"> +<p>我们没有指定 React 模块的路径——表明它并非来自本地文件,而是在 <code>package.json</code> 文件中列为依赖项。在整个学习过程中,请务必留心这两种引入方式的不同之处。</p> +</div> + +<p>第三行引入了我们的组件所需的 CSS 文件。与上面两句不同,这里没有将引入的内容赋给任何变量、也没有用到 <code>from</code> 指令。请注意这种特殊的语法并非原生 JS 的语法 —— 它源自前端资源打包工具 webpack,而 create-react-app 正是基于 webpack 配置而来的。</p> + +<div class="blockIndicator note"> +<p>译者补充:webpack 可用于打包 JS 和非 JS 的内容(当然,非 JS 的内容需要一些插件或加载器来处理),但是 JavaScript 标准只有关于 JS 的内容,所以 webpack 社区使用这种特殊的 `import` 语句来声明对非 JS 内容的引用。</p> + +<p>详情参见 webpack 官方和社区,截止目前(2020年下旬),webpack 仍是现代前端工作中必不可少的技能之一。</p> +</div> + +<h3 id="App_组件"><code>App</code> 组件</h3> + +<div class="hidden"> +<p>在 import 所需资源之后,我们定义了一个名为 App 的函数,尽管大部分 JavaScript 社区推崇使用camel命名法,如:“helloWorld”。但React组件使用pascal变量命名法,如“HelloWorld”,来帮助用户辨认一个JSX元素是React组件而非普通的HTML标签。如果您将函数名“App”改为“app”,您的浏览器会显示错误。(时间不够,先翻译到这里了)</p> +</div> + +<p>After the imports, we have a function named <code>App</code>. Whereas most of the JavaScript community prefers camel-case names like <code>helloWorld</code>, React components use pascal-case variable names, like <code>HelloWorld</code>, to make it clear that a given JSX element is a React component, and not a regular HTML tag. If you were to rename the <code>App</code> function to <code>app</code>, your browser would show you an error.</p> + +<p>让我们进一步看下App方法。</p> + +<pre class="brush: js notranslate">function App() { + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Edit <code>src/App.js</code> and save to reload. + </p> + <a + className="App-link" + href="https://reactjs.org" + target="_blank" + rel="noopener noreferrer" + > + Learn React + </a> + </header> + </div> + ); +}</pre> + +<p>App方法返回一个JSX表达式,这个表达式定义了浏览器最终要渲染的DOM。</p> + +<p>表达式中的元素就像以前写的HTML一样,都拥有属性。</p> + +<p>Some elements in the expression have attributes, which are written just like in HTML, following a pattern of <code>attribute="value"</code>. On line 3, the opening <code><a href="/en-US/docs/Web/HTML/Element/div"><div></a></code> tag has a <code>className</code> attribute. This the same as the <code><a href="/en-US/docs/Web/HTML/Global_attributes/class">class</a></code> attribute in HTML, but because JSX is JavaScript, we can't use the word <code>class</code> – it's reserved, meaning JavaScript already uses it for a specific purpose and it would cause problems here in our code. A few other HTML attributes are written differently in JSX than they are in HTML too, for the same kind of reason. We'll cover them as we encounter them.</p> + +<p>Take a moment to change the <code><a href="/en-US/docs/Web/HTML/Element/p"><p></a></code> tag on line 6 so that it reads "Hello, world!", then save your file. You'll notice that this change is immediately rendered in the development server running at <code>http://localhost:3000</code> in your browser. Now delete the <code><a href="/en-US/docs/Web/HTML/Element/a"><a></a></code> tag and save; the "Learn React" link will be gone.</p> + +<p>Your <code>App</code> component should now look like this:</p> + +<pre class="brush: js notranslate">function App() { + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Hello, World! + </p> + </header> + </div> + ); +}</pre> + +<h3 id="Export_statements">Export statements</h3> + +<p>At the very bottom of the <code>App.js</code> file, the statement <code>export default App</code> makes our <code>App</code> component available to other modules.</p> + +<h2 id="Interrogating_the_index">Interrogating the index</h2> + +<p>Let’s open <code>src/index.js</code>, because that's where the <code>App</code> component is being used. This file is the entry point for our app, and it initially looks like this:</p> + +<pre class="brush: js notranslate">import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render(<App />, document.getElementById('root')); + +// If you want your app to work offline and load faster, you can change +// unregister() to register() below. Note this comes with some pitfalls. +// Learn more about service workers: https://bit.ly/CRA-PWA +serviceWorker.unregister();</pre> + +<p>As with <code>App.js</code>, the file starts by importing all the JS modules and other assets it needs to run. <code>src/index.css</code> holds global styles that are applied to our whole app. We can also see our <code>App</code> component imported here; it is made available for import thanks to the <code>export</code> statement at the bottom of <code>App.js</code>.</p> + +<p>Line 7 calls React’s <code>ReactDOM.render()</code> function with two arguments:</p> + +<ul> + <li>The component we want to render, <code><App /></code> in this case.</li> + <li>The DOM element inside which we want the component to be rendered, in this case the element with an ID of <code>root</code>. If you look inside <code>public/index.html</code>, you'll see that this is a <code><div></code> element just inside the <code><body></code>.</li> +</ul> + +<p>All of this tells React that we want to render our React application with the <code>App</code> component as the root, or first component.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: In JSX, React components and HTML elements must have closing slashes. Writing just <code><App></code> or just <code><img></code> will cause an error.</p> +</div> + +<p><a href="/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers">Service workers</a> are interesting pieces of code that help application performance and allow features of your web applications to work offline, but they’re not in scope for this article. You can delete line 5, as well as lines 9 through 12.</p> + +<p>Your final <code>index.js</code> file should look like this:</p> + +<pre class="brush: js notranslate">import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; + +ReactDOM.render(<App />, document.getElementById('root'));</pre> + +<h2 id="Variables_and_props">Variables and props</h2> + +<p>Next, we'll use a few of our JavaScript skills to get a bit more comfortable editing components and working with data in React. We'll talk about how variables are used inside JSX, and introduce props, which are a way of passing data into a component (which can then be accessed using variables).</p> + +<h3 id="Variables_in_JSX">Variables in JSX</h3> + +<p>Back in <code>App.js</code>, let’s focus on line 9:</p> + +<pre class="brush: js notranslate"><img src={logo} className="App-logo" alt="logo" /></pre> + +<p>Here, the <code><img /></code> tag's <code>src</code> attribute value is in curly braces. This is how JSX recognizes variables. React will see <code>{logo}</code>, know you are referring to the logo import on line 2 of our app, then retrieve the logo file and render it.</p> + +<p>Let's try making a variable of our own. Before the return statement of <code>App</code>, add <code>const subject = 'React';</code>. Your <code>App</code> component should now look like this:</p> + +<pre class="brush: js notranslate">function App() { + const subject = "React"; + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Hello, World! + </p> + </header> + </div> + ); +}</pre> + +<p>Change line 8 to use our <code>subject</code> variable instead of the word "world", like this:</p> + +<pre class="brush: js notranslate">function App() { + const subject = "React"; + return ( + <div className="App"> + <header className="App-header"> + <img src={logo} className="App-logo" alt="logo" /> + <p> + Hello, {subject}! + </p> + </header> + </div> + ); +}</pre> + +<p>When you save, your browser should display "Hello, React!" instead of "Hello, world!"</p> + +<p>Variables are convenient, but the one we've just set doesn’t make great use of React's features. That's where props come in.</p> + +<h3 id="Component_props">Component props</h3> + +<p>A <strong>prop</strong> is any data passed into a React component. Props are written inside component calls, and use the same syntax as HTML attributes — <code>prop="value"</code>. Let’s open <code>index.js</code> and give our <code><App/></code> call its first prop.</p> + +<p>Add a prop of <code>subject</code> to the <code><App/></code> component call, with a value of <code>Clarice</code>. When you are done, your code should look something like this:</p> + +<pre class="brush: js notranslate">ReactDOM.render(<App subject="Clarice" />, document.getElementById('root'));</pre> + +<p>Back in <code>App.js</code>, let's revisit the App function itself, which reads like this (with the <code>return</code> statement shortened for brevity):</p> + +<pre class="brush: js notranslate">function App() { + const subject = "React"; + return ( + // return statement + ); +}</pre> + +<p>Change the signature of the <code>App</code> function so that it accepts <code>props</code> as a parameter. Just like any other parameter, you can put <code>props</code> in a <code>console.log()</code> to read it out to your browser's console. Go ahead and do that after your <code>subject</code> constant but before the <code>return</code> statement, like so:</p> + +<pre class="brush: js notranslate">function App(props) { + const subject = "React"; + console.log(props); + return ( + // return statement + ); +}</pre> + +<p>Save your file and check your browser's JavaScript console. You should see something like this logged:</p> + +<pre class="brush: js notranslate">Object { subject: "Clarice" }</pre> + +<p>The object property <code>subject</code> corresponds to the <code>subject</code> prop we added to our <code><App /></code> component call, and the string <code>Clarice</code> corresponds to its value. Component props in React are always collected into objects in this fashion.</p> + +<p>Now that <code>subject</code> is one of our props, let's utilize it in <code>App.js</code>. Change the <code>subject</code> constant so that, instead of defining it as the string <code>React</code>, you are reading the value of <code>props.subject</code>. You can also delete your <code>console.log()</code> if you want.</p> + +<pre class="brush: js notranslate">function App(props) { + const subject = props.subject; + return ( + // return statement + ); +}</pre> + +<p>When you save, the the app should now greet you with "Hello, Clarice!". If you return to <code>index.js</code>, edit the value of <code>subject</code>, and save, your text will change.</p> + +<h2 id="Summary">Summary</h2> + +<p>This brings us to the end of our initial look at React, including how to install it locally, creating a starter app, and how the basics work. In the next article we'll start building our first proper application — a todo list. Before we do that, however, let's recap some of the things we’ve learned.</p> + +<p>In React:</p> + +<ul> + <li>Components can import modules they need, and must export themselves at the bottom of their files.</li> + <li>Component functions are named with <code>PascalCase</code>.</li> + <li>You can read JSX variables by putting them between curly braces, like <code>{so}</code>.</li> + <li>Some JSX attributes are different to HTML attributes, so that they don't conflict with JavaScript reserved words. For example, <code>class</code> in HTML translates to <code>className</code> in JSX. Note that multi-word attributes are camel-cased.</li> + <li>Props are written just like attributes inside component calls, and are passed into components.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing"><font><font>在Ember中路由</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember资源和故障排除</a></li> + </ul> + </li> + <li><font><font>Vue</font></font> + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started"><font><font>Vue入门</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component"><font><font>创建我们的第一个Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists"><font><font>渲染Vue组件列表</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models"><font><font>添加新的待办事项表单:Vue事件,方法和模型</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling"><font><font>使用CSS样式化Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties"><font><font>使用Vue计算的属性</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering"><font><font>Vue条件渲染:编辑现有待办事项</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management"><font><font>使用Vue裁判进行焦点管理</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources"><font><font>Vue资源</font></font></a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_first_component/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_first_component/index.html new file mode 100644 index 0000000000..feef3ef243 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_first_component/index.html @@ -0,0 +1,384 @@ +--- +title: 创建第一个Vue组件 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">现在是时候深入了解Vue,并创建我们自己的自定义组件了--我们将从创建一个组件来表示待办事项列表中的每个项目开始。在这一过程中,我们将学习一些重要的概念,例如在其他组件中调用组件,通过道具向它们传递数据,以及保存数据状态。</p> + +<div class="blockIndicator note"> +<p class="summary"><strong>注意</strong>: 如果你需要根据我们的版本检查您的代码, 你可以在我们的<a href="https://github.com/mdn/todo-vue"> todo-vue</a> 仓库找到中找到示例 Vue 程序代码的完成版本。 有关运行中的实时版本,请参见 <a href="https://mdn.github.io/todo-vue/dist/">https://mdn.github.io/todo-vue/dist/</a>。</p> +</div> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td> + <p>熟悉<a href="https://wiki.developer.mozilla.org/zh-CN/docs/learn/HTML">HTML</a>、<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/CSS">CSS</a>、<a href="https://wiki.developer.mozilla.org/zh-CN/docs/Learn/JavaScript">JavaScript</a>核心语言,了解<a href="https://wiki.developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">终端或命令行</a>。</p> + + <p>Vue组件是由管理应用程序数据的JavaScript对象和映射到基础DOM结构的基于HTML的模板语法组成的。对于安装,以及使用Vue的一些更高级的功能(如单文件组件或渲染函数),你需要一个安装了node和npm的终端。</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>学习如何创建一个Vue组件,将其渲染到另一个组件中,使用<a href="https://vuejs.org/v2/api/#props">props</a>将数据传递到组件中,并保存其状态。</td> + </tr> + </tbody> +</table> + +<h2 id="创建一个ToDoItem组件">创建一个ToDoItem组件</h2> + +<p>让我们创建第一个组件,它将显示一个单一的待办事项。我们将用它来建立我们的待办事项列表。</p> + +<ol> + <li>在你的<code>moz-todo-vue/src/components</code>目录下,创建一个<code>ToDoItem.vue</code>的新文件。在你的代码编辑器中打开该文件。</li> + <li>通过在文件顶部添加<code><template></template></code>来创建组件的模板部分。</li> + <li>在你的模板部分下面创建一个<code><script></script></code>部分。在<code><script></code>标签内,添加一个默认导出对象<code>export default {}</code>,这是你的组件对象。</li> +</ol> + +<p>你的文件现在应该是这样的:</p> + +<pre class="brush: html notranslate"><template> </template> +<script> + export default {}; +</script></pre> + +<p>现在我们可以开始为<code>ToDoItem</code>添加实际内容了。Vue模板目前只允许一个根元素--一个元素需要包裹模板内的所有内容(Vue 3 发布后会改变这种情况)。我们将为该根元素使用一个<code><a href="https://wiki.developer.mozilla.org/zh-CN/docs/Web/HTML/Element/div"><div></a></code>。</p> + +<ol> + <li> + <p>现在在你的组件模板中添加一个空的<code><div></code>。</p> + </li> + <li> + <p>在那个<code><div></code>里面,让我们添加一个<code>checkbox</code>和一个对应的<code>label</code>。给复选框添加一个<code>id</code>,并添加一个<code>for</code>属性,将复选框映射到标签上,如下图所示。.</p> + + <pre class="brush: html notranslate"><template> + <div> + <input type="checkbox" id="todo-item" checked="false" /> + <label for="todo-item">My Todo Item</label> + </div> +</template></pre> + </li> +</ol> + +<h3 id="在应用程序中使用TodoItem组件">在应用程序中使用TodoItem组件</h3> + +<p>这一切都很顺利,但我们还没有将组件添加到我们的应用程序中,所以没有办法测试它,看看一切是否正常。我们现在就把它添加进去吧。</p> + +<ol> + <li> + <p>再次打开<code>App.vue</code>文件。</p> + </li> + <li> + <p>在<code><script></code>标签的顶部,添加以下内容来引入<code>ToDoItem</code>组件:</p> + + <pre class="brush: js notranslate">import ToDoItem from './components/ToDoItem.vue';</pre> + </li> + <li> + <p>在你的组件对象里面,添加 <code>components</code> 属性,然后在它里面添加您的ToDoItem组件进行注册。</p> + </li> +</ol> + +<p>你的<code><script></code>内容现在应该是这样的:</p> + +<pre class="brush: js notranslate">import ToDoItem from './components/ToDoItem.vue'; + +export default { + name: 'app', + components: { + ToDoItem + } +};</pre> + +<p>这和之前Vue CLI注册<code>HelloWorld</code>组件的方式是一样的。</p> + +<p>要在应用程序中实际展示<code>ToDoItem</code>组件,你需要在<code><template></code>模板内添加一个<code><to-do-item>/to-do-item></code>元素。请注意,组件文件名及其在JavaScript中的表示方式总是用大写驼色(例如<code>ToDoList</code>),而等价的自定义元素总是用连字符小写(例如<code><to-do-list></code>)。</p> + +<ol> + <li>在<code><h1></code>下面,创建一个无序列表(<code><ul></code>),其中包含一个列表项(<code><li></code>)。</li> + <li>在列表项(<font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><li></span></font>)里面添加<code><to-do-item></to-do-item></code>.</li> +</ol> + +<p>你的<code>App.vue</code>的<code><template></code>内容现在应该是这样的:</p> + +<pre class="brush: html notranslate"><div id="app"> + <h1>To-Do List</h1> + <ul> + <li> + <to-do-item></to-do-item> + </li> + </ul> +</div></pre> + +<p>如果你再次查看你的应用程序的渲染情况,你现在应该看的到渲染的<code>ToDoItem</code>组件,由一个复选框和一个标签组成。</p> + +<p><img alt="The current rendering state of the app, which includes a title of To-Do List, and a single checkbox and label" src="https://mdn.mozillademos.org/files/17243/rendered-todoitem.png" style="border-style: solid; border-width: 1px; display: block; height: 142px; margin: 0 auto; width: 385px;"></p> + +<h2 id="Making_components_dynamic_with_props">Making components dynamic with props</h2> + +<p>Our <code>ToDoItem</code> component is still not very useful because we can only really include this once on a page (IDs need to be unique), and we have no way to set the label text. Nothing about this is dynamic.</p> + +<p>What we need is some component state. This can be achieved by adding props to our component. You can think of props as being similar to inputs in a function. The value of a prop gives components an initial state that affects their display.</p> + +<h3 id="Registering_props">Registering props</h3> + +<p>In Vue, there are two ways to register props:</p> + +<ul> + <li>The first way is to just list props out as an array of strings. Each entry in the array corresponds to the name of a prop.</li> + <li>The second way is to define props as an object, with each key corresponding to the prop name. Listing props as an object allows you to specify default values, mark props as required, perform basic object typing (specifically around JavaScript primitive types), and perform simple prop validation.</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Prop validation only happens in development mode, so you can't strictly rely on it in production. Additionally, prop validation functions are invoked before the component instance is created, so they do not have access to the component state (or other props).</p> +</div> + +<p>For this component, we’ll use the object registration method.</p> + +<ol> + <li>Go back to your <code>ToDoItem.vue</code> file.</li> + <li>Add a <code>props</code> property inside the export <code>default {}</code> object, which contains an empty object.</li> + <li>Inside this object, add two properties with the keys <code>label</code> and <code>done</code>.</li> + <li>The <code>label</code> key's value should be an object with 2 properties (or <strong>props</strong>, as they are called in the context of being available to the components). + <ol> + <li>The first is a <code>required</code> property, which will have a value of <code>true</code>. This will tell Vue that we expect every instance of this component to have a label field. Vue will warn us if a <code>ToDoItem</code> component does not have a label field.</li> + <li>The second property we'll add is a <code>type</code> property. Set the value for this property as the JavaScript <code>String</code> type (note the capital "S"). This tells Vue that we expect the value of this property to be a string.</li> + </ol> + </li> + <li>Now on to the <code>done</code> prop. + <ol> + <li>First add a <code>default</code> field, with a value of <code>false</code>. This means that when no <code>done</code> prop is passed to a <code>ToDoItem</code> component, the <code>done</code> prop will will have a value of false (bear in mind that this is not required — we only need <code>default</code> on non-required props).</li> + <li>Next add a <code>type</code> field with a value of <code>Boolean</code>. This tells Vue we expect the value prop to be a JavaScript boolean type.</li> + </ol> + </li> +</ol> + +<p>Your component object should now look like this:</p> + +<pre class="brush: js notranslate"><script> + export default { + props: { + label: { required: true, type: String }, + done: { default: false, type: Boolean } + } + }; +</script></pre> + +<h3 id="Using_registered_props">Using registered props</h3> + +<p>With these props defined inside the component object, we can now use these variable values inside our template. Let's start by adding the <code>label</code> prop to the component template.</p> + +<p>In your <code><template></code>, replace the contents of the <code><label></code> element with <code>\{{label}}</code>.</p> + +<p><code>\{{}}</code> is a special template syntax in Vue, which lets us print the result of JavaScript expressions defined in our class, inside our template, including values and methods. It’s important to know that content inside <code>\{{}}</code> is displayed as text and not HTML. In this case, we’re printing the value of the <code>label</code> prop.</p> + +<p>Your component’s template section should now look like this:</p> + +<pre class="brush: html notranslate"><template> + <div> + <input type="checkbox" id="todo-item" checked="false" /> + <label for="todo-item">\{{label}}</label> + </div> +</template></pre> + +<p>Go back to your browser and you'll see the todo item rendered as before, but without a label (oh no!). Go to your browser's DevTools and you’ll see a warning along these lines in the console:</p> + +<pre class="notranslate">[Vue warn]: Missing required prop: "label" + +found in + +---> <ToDoItem> at src/components/ToDoItem.vue + <App> at src/App.vue + <Root> +</pre> + +<p>This is because we marked the <code>label</code> as a required prop, but we never gave the component that prop — we've defined where inside the template we want it used, but we haven't passed it into the component when calling it. Let’s fix that.</p> + +<p>Inside your <code>App.vue </code>file, add a <code>label</code> prop to the <code><to-do-item></to-do-item></code> component, just like a regular HTML attribute:</p> + +<pre class="brush: html notranslate"><to-do-item label="My ToDo Item"></to-do-item></pre> + +<p>Now you'll see the label in your app, and the warning won't be spat out in the console again.</p> + +<p>So that's props in a nutshell. Next we'll move on to how Vue persists data state.</p> + +<h2 id="Vues_data_object">Vue's data object</h2> + +<p>If you change the value of the <code>label</code> prop passed into the <code><to-do-item></to-do-item></code> call in your App component, you should see it update. This is great. We have a checkbox, with an updatable label. However, we're currently not doing anything with the "done" prop — we can check the checkboxes in the UI, but nowhere in the app are we recording whether a todo item is actually done.</p> + +<p>To achieve this, we want to bind the component's <code>done</code> prop to the <code>checked</code> attribute on the <code><a href="/en-US/docs/Web/HTML/Element/input"><input></a></code> element, so that it can serve as a record of whether the checkbox is checked or not. However, it's important that props serve as one-way data binding — a component should never alter the value of its own props. There are a lot of reasons for this. In part, components editing props can make debugging a challenge. If a value is passed to multiple children, it could be hard to track where the changes to that value were coming from. In addition, changing props can cause components to re-render. So mutating props in a component would trigger the component to rerender, which may in-turn trigger the mutation again.</p> + +<p>To work around this, we can manage the <code>done</code> state using Vue’s <code>data</code> property. The <code>data</code> property is where you can manage local state in a component, it lives inside the component object alongside the <code>props</code> property and has the following structure:</p> + +<pre class="brush: js notranslate">data() { + return { + key: value + } +}</pre> + +<p>You'll note that the <code>data</code> property is a function. This is to keep the data values unique for each instance of a component at runtime — the function is invoked separately for each component instance. If you declared data as just an object, all instances of that component would share the same values. This is a side-effect of the way Vue registers components and something you do not want.</p> + +<p>You use <code>this</code> to access a component's props and other properties from inside data, as you may expect. We'll see an example of this shortly.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Because of the way that <code>this</code> works in arrow functions (binding to the parent’s context), you wouldn’t be able to access any of the necessary attributes from inside <code>data</code> if you used an arrow function. So don’t use an arrow function for the <code>data</code> property.</p> +</div> + +<p>So let's add a <code>data</code> property to our <code>ToDoItem</code> component. This will return an object containing a single property that we'll call <code>isDone</code>, whose value is <code>this.done</code>.</p> + +<p>Update the component object like so:</p> + +<pre class="brush: js notranslate">export default { + props: { + label: { required: true, type: String }, + done: { default: false, type: Boolean } + }, + data() { + return { + isDone: this.done + }; + } +};</pre> + +<p>Vue does a little magic here — it binds all of your props directly to the component instance, so we don’t have to call <code>this.props.done</code>. It also binds other attributes (<code>data</code>, which you’ve already seen, and others like <code>methods</code>, <code>computed</code>, etc.) directly to the instance. This is, in part, to make them available to your template. The down-side to this is that you need to keep the keys unique across these attributes. This is why we called our <code>data</code> attribute <code>isDone</code> instead of <code>done</code>.</p> + +<p>So now we need to attach the <code>isDone</code> property to our component. In a similar fashion to how Vue uses <code>\{{}}</code> expressions to display JavaScript expressions inside templates, Vue has a special syntax to bind JavaScript expressions to HTML elements and components: <code><strong>v-bind</strong></code>. The <code>v-bind</code> expression looks like this:</p> + +<pre class="notranslate">v-bind:attribute="expression"</pre> + +<p>In other words, you prefix whatever attribute/prop you want to bind to with <code>v-bind:</code>. In most cases, you can use a shorthand for the <code>v-bind</code> property, which is to just prefix the attribute/prop with a colon. So <code>:attribute="expression"</code> works the same as <code>v-bind:attribute="expression"</code>.</p> + +<p>So in the case of the checkbox in our <code>ToDoItem</code> component, we can use <code>v-bind</code> to map the <code>isDone</code> property to the <code>checked</code> attribute on the <code><input></code> element. Both of the following are equivalent:</p> + +<pre class="brush: html notranslate"><input type="checkbox" id="todo-item" v-bind:checked="isDone" /> + +<input type="checkbox" id="todo-item" :checked="isDone" /></pre> + +<p>You're free to use whichever pattern you would like. It's best to keep it consistent though. Because the shorthand syntax is more commonly used, this tutorial will stick to that pattern.</p> + +<p>So let's do this. Update your <code><input></code> element now to replace <code>checked="false"</code> with <code>:checked="isDone"</code>.</p> + +<p>Test out your component by passing <code>:done="true"</code> to the <code>ToDoItem</code> call in <code>App.vue</code>. Note that you need to use the <code>v-bind</code> syntax, because otherwise <code>true</code> is passed as a string. The displayed checkbox should be checked.</p> + +<pre class="brush: js notranslate"><template> + <div id="app"> + <h1>My To-Do List</h1> + <ul> + <li> + <to-do-item label="My ToDo Item" :done="true"></to-do-item> + </li> + </ul> + </div> +</template> +</pre> + +<p>Try changing <code>true</code> to <code>false</code> and back again, reloading your app in between to see how the state changes.</p> + +<h2 id="Giving_Todos_a_unique_id">Giving Todos a unique id</h2> + +<p>Great! We now have a working checkbox where we can set the state programmatically. However, we can currently only add one <code>ToDoList</code> component to the page because the <code>id</code> is hardcoded. This would result in errors with assistive technology since the <code>id</code> is needed to correctly map labels to their checkboxes. To fix this, we can programmatically set the <code>id</code> in the component data.</p> + +<p>We can use the <a href="https://www.npmjs.com/package/lodash">lodash</a> package's <code>uniqueid()</code> method to help keep the index unique. This package exports a function that takes in a string and appends a unique integer to the end of the prefix. This will be sufficient for keeping component <code>id</code>s unique.</p> + +<p>Let’s add the package to our project with npm; stop your server and enter the following command into your terminal:</p> + +<pre class="brush: bash notranslate">npm install --save lodash.uniqueid</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: If you prefer yarn, you could instead use <code>yarn add lodash.uniqueid</code>.</p> +</div> + +<p>We can now import this package into our <code>ToDoItem</code> component. Add the following line at the top of <code>ToDoItem.vue</code>’s <code><script></code> element:</p> + +<pre class="brush: js notranslate">import uniqueId from 'lodash.uniqueid';</pre> + +<p>Next, add add an <code>id</code> field to our data property, so the component object ends up looking like so (<code>uniqueId()</code> returns the specified prefix — <code>todo-</code> — with a unique string appended to it):</p> + +<pre class="brush: js notranslate">import uniqueId from 'lodash.uniqueid'; + +export default { + props: { + label: { required: true, type: String }, + done: { default: false, type: Boolean } + }, + data() { + return { + isDone: this.done, + id: uniqueId('todo-') + }; + } +};</pre> + +<p>Next, bind the <code>id</code> to both our checkbox’s <code>id</code> attribute and the label’s <code>for</code> attribute, updating the existing <code>id</code> and <code>for</code> attributes as shown:</p> + +<pre class="brush: js notranslate"><template> + <div> + <input type="checkbox" :id="id" :checked="isDone" /> + <label :for="id">\{{label}}</label> + </div> +</template></pre> + +<h2 id="Summary">Summary</h2> + +<p>And that will do for this article. At this point we have a nicely-working <code>ToDoItem</code> component that can be passed a label to display, will store its checked state, and will be rendered with a unique <code>id</code> each time it is called. You can check if the unique <code>id</code>s are working by temporarily adding more <code><to-do-item></to-do-item></code> calls into <code>App.vue</code>, and then checking their rendered output with your browser's DevTools.</p> + +<p><span class="author-d-1gg9uz65z1iz85zgdz68zmqkz84zo2qoxwoxz78zz83zz84zz69z2z80zgwxsgnz83zfkt5e5tz70zz68zmsnjz122zz71z">Now we're ready to add multiple <code>ToDoItem</code> components to our App. In our next article we'll look at adding a set of todo item data to our <code>App.vue</code> component, which we'll then loop through and display inside <code>ToDoItem</code> components using the <code>v-for</code> directive. </span></p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_getting_started/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_getting_started/index.html new file mode 100644 index 0000000000..c4ad60834a --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_getting_started/index.html @@ -0,0 +1,288 @@ +--- +title: 开始使用 Vue +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">现在让我们介绍Vue,我们的第三个框架。在本文中,我们将了解一点Vue的背景知识,了解如何安装它并创建一个新项目,研究整个项目和单个组件的高级结构,了解如何在本地运行项目,并为开始构建示例做好准备。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td> + <p>熟悉核心的HTML, CSS和JavaScript语言,了解终端/命令行。</p> + + <p>Vue组件设计成为由管理应用数据的JavaScript对象和映射到底层DOM结构的html模板语法组成的混合体。安装和使用的一些更高级的功能Vue(如单文件组件或渲染函数),你将需要一个安装了node和npm的终端。</p> + </td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>设置本地Vue开发环境,创建一个starter应用程序,并了解它是如何工作的基础知识。</td> + </tr> + </tbody> +</table> + +<h2 id="一个更简洁的框架——Vue">一个更简洁的框架——Vue</h2> + +<p>Vue是一个现代JavaScript框架提供了有用的设施渐进增强——不像许多其他框架,您可以使用Vue增强现有的HTML。这使您可以使用Vue作为<a href="https://mdn.mozillademos.org/files/17240/vue-default-app.png">jQuery</a>等库的临时替代品。</p> + +<p>也就是说,您还可以使用Vue编写整个单页应用程序(SPAs)。这允许您创建标记完全由Vue管理,可以提高开发人员的经验和性能在处理复杂的应用程序。当你需要的时候它还允许您利用其他库对客户端路由和状态进行管理。此外,Vue需要“中间地带”的方法工具客户端路由和状态管理。虽然Vue核心团队维护了建议的函数库,但他们并没有直接捆绑到 Vue 里。这样你就可以选择一个其他路由/状态管理库,来更好地适应您的应用程序。</p> + +<p>除了允许您逐步将Vue集成到您的应用程序中,Vue还提供了一种渐进的方式编写标记。像大多数框架,Vue通过组件允许您创建可重用块标记。大多数时候,Vue组件是使用一个特殊的HTML模板的语法写的。当您需要比HTML语法允许的更多的控制时,您可以编写JSX或纯JavaScript函数来定义组件。</p> + +<p>在学习本教程的过程中,您可能希望在其他选项卡中打开<a href="https://cn.vuejs.org/v2/guide/index.html">Vue指南</a>和<a href="https://cn.vuejs.org/v2/api/index.html">API文档</a>,这样,如果您想了解更多信息,可以参考它们。</p> + +<p>要想对Vue和许多其他框架进行比较(但可能存在偏差),请参阅Vue文档:<a href="https://cn.vuejs.org/v2/guide/comparison.html">与其他框架的比较。</a></p> + +<h2 id="安装Vue">安装Vue</h2> + +<p>要在现有站点中使用Vue,可以通过<a href="https://wiki.developer.mozilla.org/en-US/docs/Web/HTML/Element/script"><script></a>元素在页面中使用。这使您可以开始在现有站点上使用Vue,这就是Vue引以为傲的渐进式框架的原因。当使用JQuery这样的库将现有项目迁移到Vue时,这是一个很好的选择。通过这种方法,您可以使用Vue的许多核心功能,例如属性、自定义组件和数据管理。</p> + +<ul> + <li> + <p>开发环境版本,包含了有帮助的命令行警告</p> + + <pre class="brush: html notranslate"><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script></pre> + </li> + <li> + <p>生产环境版本,优化了尺寸和速度,建议您在站点上包含Vue时指定版本号,这样任何框架更新都不会影响您的网站。</p> + + <pre class="brush: html notranslate"><script src="https://cdn.jsdelivr.net/npm/vue@2"></script></pre> + </li> +</ul> + +<p>然而,这种方法有一些局限性。要构建更复杂的应用程序,您需要使用 <a href="https://www.npmjs.com/package/vue">Vue NPM package</a>。这将允许您使用Vue的高级功能并利用WebPack等捆绑包。为了使使用Vue构建应用程序更容易,有一个CLI来简化开发过程。要使用npm软件包和CLI,您需要:</p> + +<ol> + <li>Node.js 8.11+ installed.</li> + <li>npm or yarn.</li> +</ol> + +<div class="blockIndicator note"> +<p><strong>注意:如果您还没有安装上述软件,有关安装NPM和Node.js的方法在这里</strong></p> +</div> + +<p>安装CLI,终端中运行以下命令:</p> + +<pre class="brush: bash notranslate">npm install --global @vue/cli</pre> + +<p>如果您使用 yarn:</p> + +<pre class="brush: bash notranslate">yarn global add @vue/cli</pre> + +<p>安装之后,要初始化一个新项目,可以在要创建项目的目录中打开一个终端,并运行vue create <project-name>。CLI将会给你一个可以使用的项目配置列表。有一些预设的,你可以按照自己的需求。这些选项允许您配置TypeScript,linting,vue-router,testing,和更多。</p> + +<ul> + <li> + <p>下面我们来看看使用这个。</p> + </li> +</ul> + +<h2 id="初始化一个新项目">初始化一个新项目</h2> + +<p>为了探索Vue中各种各样的特征,我们将建立一个简单的任务清单应用。我们将会使用Vue脚手架工具去创建一个新的应用框架并在上面搭建我们的应用。请按照以下步骤:</p> + +<ol> + <li>在终端,用cd命令进入你想要创建示例的文件夹,然后执行 <code>vue create moz-todo-vue.</code></li> + <li>使用方向键然后 <kbd>Enter</kbd> 选择 "Manually select features(手动选择功能)" 选项.</li> + <li>你会看到的第一个菜单允许你选择你想要包含在你的项目中的功能。确保 "Babel" 和 "Linter / Formatter" 这两项是被选中的. 如果他们没有被选中,使用方向键切换,空格键选中,一旦他们被选中按下 <kbd>Enter</kbd> 继续进行。</li> + <li>接下来你要为linter / formatter选择一个配置。切换选中"Eslint with error prevention only"然后再次按下 <kbd>Enter</kbd> 。这样辅助我们捕获常见的并且不自以为是的错误。</li> + <li>然后你会被询问需要那种自动化的 lint,选择 "Lint on save",这样我们在项目中保存文件的时候就会自动检查错误。按下 <kbd>Enter</kbd> 继续。</li> + <li>接着你将需要选择把配置文件放在哪里。"In dedicated config files" 这个选项会把你的配置文件比如 ESLint 单独放在一个文件里。另一个选项 "In package.json" 则会把配置放进项目的 package.json 文件里。选择 "In dedicated config files" 然后使劲敲下 <kbd>Enter</kbd>。</li> + <li>最后会问你,是否选择把本次的选择作为将来的一个预设配置(Save this as a preset for future projects?),这个就安全由你自己决定了。如果你想把本次的配置作为一个预设配置并且以后想再次使用的话,按下 <kbd>y</kbd> , 否则按下 <kbd>n</kbd>。</li> +</ol> + +<p>然后脚手架工具就开始构建项目,并且安装所需的依赖。</p> + +<p>如果你以前从来没有使用过 Vue CLI 的话,你可能还会遇到其他的问题,会让你选择包管理器,你可以通过方向键选择一个你喜欢的,然后从此时起 Vue CLI 就会默认使用你这次选择的包管理器。如果你只后又想换用其他包管理器,你也可以在创建项目执行 <code>vue create</code> 的时候传入参数 <code>--packageManager=<package-manager></code>。比如你之前已经选择了 yarn,而你现在在创建 <code>moz-todo-vue</code> 示例程序的时候可以通过执行 <code>vue create moz-todo-vue --packageManager=npm</code> 来使用 npm 包管理器。</p> + +<div class="blockIndicator note"> +<p><strong>注意:我们这里并没有列举所有的配置项,如果你想了解更多信息可以访问</strong> <a href="https://cli.vuejs.org/">Vue 官方文档</a>的 CLI 部分。</p> +</div> + +<h2 id="项目结构">项目结构</h2> + +<p>如果前面的步骤都执行顺利的话,脚手架工具应该已经在你的项目中创建了一系列的文件和目录,我们接下来列举一些比较重要的:</p> + +<ul> + <li><code>.eslintrc.js</code>: 这个是 <a href="https://eslint.org/">eslint</a> 的配置文件,可以通过它来管理你的校验规则。</li> + <li><code>babel.config.js</code>: 这个是 <a href="https://babeljs.io/">Babel</a> 的配置文件,可以在开发中使用 JavaScript 的新特性,并且将其转换为在生产环境中可以跨浏览器运行的旧语法代码。你也可以在这个里配置额外的 babel 插件。 </li> + <li><code>.browserslistrc</code>: 这个是 <a href="https://github.com/browserslist/browserslist">Browserslist</a> 的配置文件,可以通过它来控制需要对哪些浏览器进行支持和优化。</li> + <li><code>public</code>: 这个目录包含一些在 <a href="https://webpack.js.org/">Webpack</a> 编译过程中没有加工处理过的文件(有一个例外:index.html 会有一些处理)。 + <ul> + <li><code>favicon.ico</code>: 这个是项目的图标,当前就是一个 Vue 的 logo。</li> + <li><code>index.html</code>: 这是应用的模板文件,Vue 应用会通过这个 HTML 页面来运行,也可以通过 lodash 这种模板语法在这个文件里插值。 + <div class="note"><strong>注意:这个不是负责管理页面最终展示的模板,而是管理 Vue 应用之外的静态 HTML 文件,一般只有在用到一些高级功能的时候才会修改这个文件。</strong></div> + </li> + </ul> + </li> + <li><code>src</code>: 这个是 Vue 应用的核心代码目录 + <ul> + <li><code>main.js</code>:这是应用的入口文件。目前它会初始化 Vue 应用并且制定将应用挂载到 <code>index.html</code> 文件中的哪个 HTML 元素上。通常还会做一些注册全局组件或者添额外的 Vue 库的操作。</li> + <li><code>App.vue</code>:这是 Vue 应用的根节点组件,往下看可以了解更多关注 Vue 组件的信息。</li> + <li><code>components</code>:这是用来存放自定义组件的目录,目前里面会有一个示例组件。</li> + <li><code>assets</code>:这个目录用来存放像 CSS 、图片这种静态资源,但是因为它们属于代码目录下,所以可以用 webpack 来操作和处理。意思就是你可以使用一些预处理比如 <a href="https://sass-lang.com/">Sass/SCSS</a> 或者 <a href="https://stylus-lang.com/">Stylus</a>。</li> + </ul> + </li> +</ul> + +<div class="blockIndicator note"> +<p><strong>注意:根据创建项目时候的一些配置项,可能会有一些其他的预设目录(例如,如果你选择了路由配置,会看到一个 <code>views</code> 的文件夹)</strong></p> +</div> + +<h2 id=".vue_文件(单文件组件)">.vue 文件(单文件组件)</h2> + +<p>就像很多其他的前端框架一样,组件是构建 Vue 应用中非常重要的一部分。组件可以把一个很大的应用程序拆分为独立创建和管理的不相关区块,然后彼此按需传递数据,这些小的代码块可以方便更容易的理解和测试。</p> + +<p>在其他框架都鼓励把模板、逻辑和样式的代码区分成不同文件的时候,Vue 却反其道行之。使用<a href="https://vuejs.org/v2/guide/single-file-components.html">单文件组件</a>,Vue 把模板、相关脚本和 CSS 一起整合放在 <code>.vue</code> 结尾的一个单文件中。这些文件最终会通过 JS 打包工具(例如 Webpack)处理,这意味着你可以使用构建时工具。你可以使用比如 Babel、TypeScript、SCSS 等来创建更多复杂的组件。</p> + +<p> 另外,使用 Vue CLI 创建的项目被配置为在开箱即用的情况下借助 Webpack 使用 .<code>vue </code>文件。实际上,如果您查看我们使用 CLI 创建的项目中的 <code>src</code> 文件夹,您会看到第一个<code>.vue</code> 文件:<code>App.vue</code>。</p> + +<p>现在我们来开始探讨。</p> + +<h3 id="App.vue">App.vue</h3> + +<p>打开 <code>App.vue</code> 文件,可以看到有三部分组成 <code><template></code>, <code><script></code>, and <code><style></code>,分别包含了组件的模板、脚本和样式相关的内容。所有的单文件组件都是这种类似的基本结构。</p> + +<p><code><template></code> 包含了所有的标记结构和组件的展示逻辑。template 可以包含任何合法的 HTML,以及一些我们接下来要讲的 Vue 特定的语法。</p> + +<div class="blockIndicator note"> +<p><strong>注意:通过设置 <font face="consolas, Liberation Mono, courier, monospace"><span style="background-color: rgba(220, 220, 220, 0.5);"><template></span></font> 标签的 <code>lang</code> 属性,例如可以通过设置 </strong><code><template lang="pug"></code> 就可以在使用 Pub 模板来替代标准 HTML。在本教程中我们依然会使用标准 HTML,但是这种方式可行你还是值得知道。</p> +</div> + +<p><code><script></code> 包含组件中所有的非显示逻辑,最重要的是, <code><script></code> 标签需要默认导出一个 JS 对象。该对象是您在本地注册组件、定义属性、处理本地状态、定义方法等的地方。在构建阶段这个对象会被处理和转换(包含 template 模板)成为一个有 <code>render()</code> 函数的 Vue 组件。</p> + +<p dir="ltr" id="tw-target-text">对于 <code>App.vue</code>,我们的默认导出将组件的名称设置为 <code>app</code> ,并通过将 <code>HelloWorld</code> 组件添加到 <code>components</code> 属性中来注册它。以这种方式注册组件时,就是在本地注册。本地注册的组件只能在注册它们的组件内部使用,因此您需要将其导入并注册到使用它们的每个组件文件中。这对于拆包 / tree shaking(译者注:一种减小包体积优化方式)很有用,因为并不是应用程序中的每个页面都不一定需要每个组件。</p> + +<pre class="brush: js notranslate">import HelloWorld from './components/HelloWorld.vue'; + +export default { + name: 'app', + components: { + //You can register components locally here. + HelloWorld + } +};</pre> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 如果想要使用 <a href="https://www.typescriptlang.org/">TypeScript</a> 语法,你应该将 <code><script></code> 标签的 <code>lang</code> 属性设置为 <code><script lang="ts"></code> 来告诉编译器你要使用 TypeScript。</p> +</div> + +<p>组件的 CSS 应该写在 <code><style></code> 标签里,如果你添加了 <code>scoped</code> 属性,形如 <code><style scoped></code> ,Vue 会把样式的范围限制到单文件组件的内容里。这个类似 CSS-in-JS 的解决方案,不过允许你书写文本格式的 CSS了。</p> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 如果通过 CLI 创建项目时选择了 CSS 预处理器,则可以将 <code>lang</code> 属性添加到 <code><style></code> 标签中,以便 Webpack 可以在构建时处理内容。例如,<code><style lang ="scss"></code> 将允许您在样式信息中使用 SCSS 语法。</p> +</div> + +<h2 id="本地运行程序">本地运行程序</h2> + +<p>Vue CLI 带有内置的开发服务器。这样一来,您就可以在本地运行您的应用程序,这样就可以轻松对其进行测试,而无需自己配置服务器。 CLI 会以 npm 脚本的形式将 <code>serve</code> 命令添加到项目的 <code>package.json </code>文件中,因此您可以轻松地运行它。</p> + +<p dir="ltr" id="tw-target-text">在您的终端中,尝试运行 <code>npm run serve</code>(或者如果希望使用 yarn ,则运行 <code>yarn serve</code> )。您的终端应输出类似以下内容的内容:</p> + +<pre class="notranslate">INFO Starting development server... +98% after emitting CopyPlugin + + DONE Compiled successfully in 18121ms + + App running at: + - Local: <http://localhost:8080/> + - Network: <http://192.168.1.9:8080/> + + Note that the development build is not optimized. + To create a production build, run npm run build.</pre> + +<p>If you navigate to the “local” address in a new browser tab (this should be something like <code>http://localhost:8080</code> as stated above, but may vary based on your setup), you should see your app. Right now, it should contain a welcome message, a link to the Vue documentation, links to the plugins you added when you initialized the app with your CLI, and some other useful links to the Vue community and ecosystem.</p> + +<p>如果在浏览器新选项卡打开“本地”地址(如上所述,该地址应类似于<code>http://localhost:8080</code>,但可能会因设置而异),您应该会看到您的应用。现在,它应该包含欢迎消息,Vue文档的链接,使用 CLI 初始化应用程序时添加的插件的链接,以及指向Vue社区和生态系统的其他有用链接。</p> + +<p><img alt="default vue app render, with vue logo, welcome message, and some documentation links" src="https://mdn.mozillademos.org/files/17240/vue-default-app.png" style="border-style: solid; border-width: 1px; height: 779px; width: 1600px;"></p> + +<h2 id="做一些改动">做一些改动</h2> + +<p>对应用作出第一步改动——删除 Vue logo。打开 <code>App.vue</code> 文件,在 template 部分删除 <code><img></code> 元素。</p> + +<pre class="brush: html notranslate"><span class="author-d-iz88z86z86za0dz67zz78zz78zz74zz68zjz80zz71z9iz90z8h7gz67ziz76zcz77zz80zz71zncfz69zz69ziaz82zz71zz72zhz77zz122zz90z14mcyd"><img alt="Vue logo" src="./assets/logo.png"></span></pre> + +<p>如果你的服务器还在正常运行,应该可以看到 logo 几乎即时从之前渲染出来的页面中消失。我们再把 <code>HelloWorld</code> 组件也删掉。</p> + +<p>线删除下面这一整行:</p> + +<pre class="brush: html notranslate"><HelloWorld msg="Welcome to Your Vue.js App"/></pre> + +<p>如果你在这个时候保存 <code>App.vue</code> 文件,渲染好的应用会因为我们注册了组件但没有使用而抛出一个错误。我们还需要在 <code><script></code> 中将引入和注册组件的那几行删掉:</p> + +<p>现在把下面这几行删掉吧:</p> + +<pre class="brush: js notranslate">import HelloWorld from './components/HelloWorld.vue'</pre> + +<pre class="brush: js notranslate">components: { + HelloWorld +}</pre> + +<p>渲染好的应用这时候将不再显示错误了,只有一个空页面,因为我们的 <code><template></code> 里没有一个可见的内容。</p> + +<p>我们接下来要创建一个任务清单的应用,在 <code><div id="app"></code> 中新增一个 <code><h1></code> 标签,并将标题文案设为 "To-Do List",参考如下:</p> + +<pre class="brush: html notranslate"><template> + <div id="app"> + <h1>To-Do List</h1> + </div> +</template></pre> + +<p><code>App.vue</code> 将会如期展示标题。</p> + +<h2 id="总结">总结</h2> + +<p>让我们在这里暂时告一段落。我们已经了解了 Vue 背后的一些想法,为示例应用程序创建了一些脚手架,使其可以运行,检查并进行了一些初步更改。</p> + +<p dir="ltr">在不进行基本介绍的情况下,我们现在将进一步研究并构建示例应用程序,这是一个基本的任务清单应用程序,它允许我们存储项目列表,完成后将其选中,并按所有、已完成和未完成待办事项来过滤。</p> + +<p dir="ltr">在下一篇文章中,我们将构建第一个自定义组件,并研究一些重要概念,例如将 prop 传递到其中和保存其 data 状态。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">客户端架构介绍</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">开始使用React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">开始我们的 React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">组件化我们的 React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events 和 state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React 资源</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">开始使用 Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">开始使用 Vue</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">创建第一个 Vue 组件</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">渲染 Vue 列表</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">添加表单:Vue 时间、方法和模型</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">用 Css 装饰 Vue 组件</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">使用 Vue 计算属性</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue 条件渲染</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">聚焦 Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue 资源</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_methods_events_models/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_methods_events_models/index.html new file mode 100644 index 0000000000..a4eb23e91b --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_methods_events_models/index.html @@ -0,0 +1,404 @@ +--- +title: 'Adding a new todo form: Vue events, methods, and models' +slug: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models +translation_of: >- + Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">We now have sample data in place, and a loop that takes each bit of data and renders it inside a <code>ToDoItem</code> in our app. What we really need next is the ability to allow our users to enter their own todo items into the app, and for that we'll need a text <code><input></code>, an event to fire when the data is submitted, a method to fire upon submission to add the data and rerender the list, and a model to control the data. This is what we'll cover in this article.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + + <p>Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with node + npm installed.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To learn about handling forms in Vue, and by association, events, models, and methods.</td> + </tr> + </tbody> +</table> + +<h2 id="Creating_a_New_To-Do_form">Creating a New To-Do form</h2> + +<p>我们的app可以展示待办事项列表,但是我们不能更新该列表,除非手动更改代码。让我们新建一个组件来允许我们添加新的待办项。</p> + +<p>在components目录下, 新建文件 <code>ToDoForm.vue</code>.</p> + +<ol> + <li> + <p>创建一个空的 <code><template></code> 和 <code><script></code> :</p> + + <pre class="brush: html notranslate"><template></template> + +<script> + export default {}; +</script></pre> + </li> + <li> + <p>新建一个 HTML 表单来允许我们输入新的待办项并把它提交到app。我们需要一个 <code><a href="/en-US/docs/Web/HTML/Element/form"><form></a></code> ,它里面包含 <code><a href="/en-US/docs/Web/HTML/Element/label"><label></a></code>,一个 <code><a href="/en-US/docs/Web/HTML/Element/input"><input></a></code>, 一个 <code><a href="/en-US/docs/Web/HTML/Element/button"><button></a></code>。更新后的模版如下:</p> + + <pre class="brush: html notranslate"><template> + <form> + <label for="new-todo-input"> + What needs to be done? + </label> + <input + type="text" + id="new-todo-input" + name="new-todo" + autocomplete="off" + /> + <button type="submit"> + Add + </button> + </form> +</template></pre> + + <p>现在我们有一个可以form组件可以用来输入新的待办项的标题,它最终会渲染成<code>ToDoItem</code>的label。</p> + </li> + <li> + <p>我们把这个组件添加到app中,返回 <code>App.vue</code> 然后在 <code><script></code> 添加下面的语句:</p> + + <pre class="brush: js notranslate">import ToDoForm from './components/ToDoForm';</pre> + </li> + <li> + <p>在你的App组件中注册它</p> + + <pre class="brush: js notranslate">components: { + ToDoItem, + ToDoForm +}</pre> + </li> + <li> + <p>最后将 <code>ToDoForm</code> 组件添加到App中的<code><template></code> 中,像下面这样:</p> + + <pre class="brush: html notranslate"><template> + <div id="app"> + <h1>My To-Do List</h1> + <to-do-form></to-do-form> + <ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item :label="item.label" :done="item.done" :id="item.id"></to-do-item> + </li> + </ul> + </div> +</template></pre> + </li> +</ol> + +<p>Now when you view your running site, you should see the new form displayed.</p> + +<p><img alt="Our todo list app rendered with a text input to enter new todos" src="https://mdn.mozillademos.org/files/17245/rendered-form-with-text-input.png" style="border-style: solid; border-width: 1px; display: block; height: 240px; margin: 0 auto; width: 451px;"></p> + +<p>If you fill it out and click the "Add" button, the page will post the form back to the server, but this isn’t really what we want. What we actually want to do is run a method on the <a href="/en-US/docs/Web/API/HTMLFormElement/submit_event"><code>submit</code> event</a> that will add the new todo to the <code>ToDoItem</code> data list defined inside <code>App</code>. To do that, we'll need to add a method to the component instance.</p> + +<h2 id="Creating_a_method_binding_it_to_an_event_with_v-on">Creating a method & binding it to an event with v-on</h2> + +<p>To make a method available to the <code>ToDoForm</code> component, we need to add it to the component object, and this is done inside a <code>methods</code> property to our component, which goes in the same place as <code>data()</code>, <code>props</code>, etc. The <code>methods</code> property holds any methods we might need to call in our component. When referenced, methods are fully run, so it's not a good idea to use them to display information inside the template. For displaying data that comes from calculations, you should use a <code>computed</code> property, which we'll cover later.</p> + +<ol> + <li> + <p>In this component, we need to add an <code>onSubmit()</code> method to a <code>methods</code> property inside the <code>ToDoForm</code> component object. We'll use this to handle the submit action.</p> + + <p>Add this like so:</p> + + <pre class="brush: js notranslate">export default { + methods: { + onSubmit() { + console.log('form submitted') + } + } +}</pre> + </li> + <li> + <p>Next we need to bind the method to our <code><form></code> element's <code>submit</code> event handler. Much like how Vue uses the <code><a href="https://vuejs.org/v2/api/#v-bind">v-bind</a></code> syntax for binding attributes, Vue has a special directive for event handling: <code><a href="https://vuejs.org/v2/api/#v-on">v-on</a></code>. The <code>v-on</code> directive works via the <code>v-on:event="method"</code> syntax. And much like <code>v-bind</code>, there’s also a shorthand syntax: <code>@event="method"</code>.</p> + + <p>We'll use the shorthand syntax here for consistency. Add the <code>submit</code> handler to your <code><form></code> element like so:</p> + + <pre class="brush: html notranslate"><form @submit="onSubmit"> +</pre> + </li> + <li> + <p>When you run this, the app still posts the data to the server, causing a refresh. Since we're doing all of our processing on the client, there's no server to handle the postback. We also lose all local state on page refresh. To prevent the browser from posting to the server, we need to stop the event’s default action from bubbling up through the page (<code><a href="/en-US/docs/Web/API/Event/preventDefault">Event.preventDefault()</a></code>, in vanilla JavaScript). Vue has a special syntax called <strong>event modifiers</strong> that can handle this for us right in our template.</p> + + <p>Modifiers are appended to the end of an event with a dot like so: <code>@event.modifier</code>. Here is a list of event modifiers:</p> + + <ul> + <li><code>.stop</code>: Stops the event from propagating. Equivalent to <code><a href="/en-US/docs/Web/API/Event/stopPropagation">Event.stopPropagation()</a></code> in regular JavaScript events.</li> + <li><code>.prevent</code>: Prevents the event's default behavior. Equivalent to <code><a href="/en-US/docs/Web/API/Event/preventDefault">Event.preventDefault()</a></code>.</li> + <li><code>.self</code>: Triggers the handler only if the event was dispatched from this exact element.</li> + <li><code>{.key}</code>: Triggers the event handler only via the specified key. <a href="/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">MDN has a list of valid key values</a>; multi-word keys just need to be converted to kebab case (e.g. <code>page-down</code>).</li> + <li><code>.native</code>: Listens for a native event on the root (outer-most wrapping) element on your component.</li> + <li><code>.once</code>: Listens for the event until it's been triggered once, and then no more.</li> + <li><code>.left</code>: Only triggers the handler via the left mouse button event.</li> + <li><code>.right</code>: Only triggers the handler via the right mouse button event.</li> + <li><code>.middle</code>: Only triggers the handler via the middle mouse button event.</li> + <li><code>.passive</code>: Equivalent to using the <code>{ passive: true }</code> parameter when creating an event listener in vanilla JavaScript using <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code>.</li> + </ul> + + <p>In this case, we need to use the <code>.prevent</code> handler to stop the browser’s default submit action. Add <code>.prevent</code> to the <code>@submit</code> handler in your template like so:</p> + + <pre class="brush: js notranslate"><form @submit.prevent="onSubmit"></pre> + </li> +</ol> + +<p>If you try submitting the form now, you'll notice that the page doesn't reload. If you open the console, you can see the results of the <code><a href="/en-US/docs/Web/API/Console/log">console.log()</a></code> we added inside our <code>onSubmit()</code> method.</p> + +<h2 id="Binding_data_to_inputs_with_v-model">Binding data to inputs with v-model</h2> + +<p>Next up, we need a way to get the value from the form's <code><input></code> so we can add the new to-do item to our <code>ToDoItems</code> data list.</p> + +<p>The first thing we need is a <code>data</code> property in our form to track the value of the to-do.</p> + +<ol> + <li> + <p>Add a <code>data()</code> method to our <code>ToDoForm</code> component object that returns a <code>label</code> field. We can set the initial value of the <code>label</code> to an empty string.</p> + + <p>Your component object should now look something like this:</p> + + <pre class="brush: js notranslate">export default { + methods: { + onSubmit() { + console.log("form submitted"); + } + }, + data() { + return { + label: "" + }; + } +};</pre> + </li> + <li> + <p>We now need some way to attach the value of the <code>new-todo-input</code> <code><input></code> field to the <code>label</code> field. Vue has a special directive for this: <code><a href="https://vuejs.org/v2/api/#v-model">v-model</a></code>. <code>v-model</code> binds to the data property you set on it and keeps it in sync with the <code><input></code>. <code>v-model</code> works across all the various input types, including check boxes, radios, and select inputs. To use <code>v-model</code>, you add an attribute with the structure <code>v-model="variable"</code> to the <code><input></code>.</p> + + <p>So in our case, we would add it to our <code>new-todo-input</code> field as seen below. Do this now:</p> + + <pre class="brush: js notranslate"><input + type="text" + id="new-todo-input" + name="new-todo" + autocomplete="off" + v-model="label" /> +</pre> + + <div class="blockIndicator note"> + <p><strong>Note</strong>: You can also sync data with <code><input></code> values through a combination of events and <code>v-bind</code> attributes. In fact, this is what <code>v-model</code> does behind the scenes. However, the exact event and attribute combination varies depending on input types and will take more code than just using the <code>v-model</code> shortcut.</p> + </div> + </li> + <li> + <p>Let's test out our use of <code>v-model</code> by logging the value of the data submitted in our <code>onSubmit()</code> method. In components, data attributes are accessed using the <code>this</code> keyword. So we access our <code>label</code> field using <code>this.label</code>.</p> + + <p>Update your <code>onSubmit()</code> method to look like this:</p> + + <pre class="brush: js notranslate">methods: { + onSubmit() { + console.log('Label value: ', this.label); + } +},</pre> + </li> + <li> + <p>Now go back to your running app, add some text to the <code><input></code> field, and click the "Add" button. You should see the value you entered logged to your console, for example:</p> + + <pre class="notranslate">Label value: My value</pre> + </li> +</ol> + +<h2 id="Changing_v-model_behavior_with_modifiers">Changing <code>v-model</code> behavior with modifiers</h2> + +<p>In a similar fashion to event modifiers, we can also add modifiers to change the behavior of <code>v-model</code>. In our case, there are two worth considering. The first, <code>.trim</code>, will remove whitespace from before or after the input. We can add the modifier to our <code>v-model</code> statement like so: <code>v-model.trim="label"</code>.</p> + +<p>The second modifier we should consider is called <code>.lazy</code>. This modifier changes when <code>v-model</code> syncs the value for text inputs. As mentioned earlier, <code>v-model</code> syncing works by updating the variable using events. For text inputs, this sync happens using the <a href="/en-US/docs/Web/API/HTMLElement/input_event"><code>input</code> event</a>. Often, this means that Vue is syncing the data after every keystroke. The <code>.lazy</code> modifier causes <code>v-model</code> to use the <a href="/en-US/docs/Web/API/HTMLElement/change_event"><code>change</code> event</a> instead. This means that Vue will only sync data when the input loses focus or the form is submitted. For our purposes, this is much more reasonable since we only need the final data.</p> + +<p>To use both the <code>.lazy</code> modifier and the <code>.trim</code> modifier together, we can chain them, e.g. <code>v-model.lazy.trim="label"</code>.</p> + +<p>Update your <code>v-model</code> attribute to chain <code>lazy</code> and <code>trim</code> as shown above, and then test your app again. Try for example, submitting a value with whitespace at each end.</p> + +<h2 id="Passing_data_to_parents_with_custom_events">Passing data to parents with custom events</h2> + +<p>We now are very close to being able to add new to-do items to our list. The next thing we need to be able to do is pass the newly-created to-do item to our <code>App</code> component. To do that, we can have our <code>ToDoForm</code> emit a custom event that passes the data, and have <code>App</code> listen for it. This works very similarly to native events on HTML elements: a child component can emit an event which can be listened to via <code>v-on</code>.</p> + +<p>In the <code>onSubmit</code> event of our <code>ToDoForm</code>, let's add a <code>todo-added</code> event. Custom events are emitted like this: <code>this.$emit("event-name")</code>. It's important to know that event handlers are case sensitive and cannot include spaces. Vue templates also get converted to lowercase, which means Vue templates cannot listen for events named with capital letters.</p> + +<ol> + <li> + <p>Replace the <code>console.log()</code> in the <code>onSubmit()</code> method with the following:</p> + + <pre class="brush: js notranslate">this.$emit("todo-added");</pre> + </li> + <li> + <p>Next, go back to <code>App.vue</code> and add a <code>methods</code> property to your component object containing an <code>addToDo()</code> method, as shown below. For now, this method can just log <code>To-do added</code> to the console.</p> + + <pre class="brush: js notranslate">export default { + name: 'app', + components: { + ToDoItem, + ToDoForm + }, + data() { + return { + ToDoItems: [ + { id:uniqueId('todo-'), label: 'Learn Vue', done: false }, + { id:uniqueId('todo-'), label: 'Create a Vue project with the CLI', done: true }, + { id:uniqueId('todo-'), label: 'Have fun', done: true }, + { id:uniqueId('todo-'), label: 'Create a to-do list', done: false } + ] + }; + }, + methods: { + addToDo() { + console.log('To-do added'); + } + } +};</pre> + </li> + <li> + <p>Next, add an event listener for the <code>todo-added</code> event to the <code><to-do-form></to-do-form></code>, which calls the <code>addToDo()</code> method when the event fires. Using the <code>@</code> shorthand, the listener would look like this: <code>@todo-added="addToDo"</code>:</p> + + <pre class="brush: html notranslate"><to-do-form @todo-added="addToDo"></to-do-form></pre> + </li> + <li> + <p>When you submit your <code>ToDoForm</code>, you should see the console log from the <code>addToDo()</code> method. This is good, but we're still not passing any data back into the <code>App.vue</code> component. We can do that by passing additional arguments to the <code>this.$emit()</code> function back in the <code>ToDoForm</code> component.</p> + + <p>In this case, when we fire the event we want to pass the <code>label</code> data along with it. this is done by including the data you want to pass as another parameter in the <code>$emit()</code> method: <code>this.$emit("todo-added", this.label)</code>. This is similar to how native JavaScript events include data, except custom Vue events include no event object by default. This means that the emitted event will directly match whatever object you submit. So in our case, our event object will just be a string.</p> + + <p>Update your <code>onSubmit()</code> method like so:</p> + + <pre class="brush: js notranslate">onSubmit() { + this.$emit('todo-added', this.label) +}</pre> + </li> + <li> + <p>To actually pick up this data inside <code>App.vue</code>, we need to add a parameter to our <code>addToDo()</code> method that includes the <code>label</code> of the new to-do item.</p> + + <p>Go back to <code>App.vue</code> and update this now:</p> + + <pre class="brush: js notranslate">methods: { + addToDo(toDoLabel) { + console.log('To-do added:', toDoLabel); + } +} +</pre> + </li> +</ol> + +<p>If you test your form again, you'll see whatever text you enter logged in your console upon submission. Vue automatically passes the arguments after the event name in <code>this.$emit()</code> to your event handler.</p> + +<h2 id="Adding_the_new_todo_into_our_data">Adding the new todo into our data</h2> + +<p>Now that we have the data from <code>ToDoForm</code> available in <code>App.vue</code>, we need to add an item representing it to the <code>ToDoItems</code> array. This can be done by pushing a new to-do item object to the array containing our new data.</p> + +<ol> + <li> + <p>Update your <code>addToDo()</code> method like so:</p> + + <pre class="brush: js notranslate">addToDo(toDoLabel) { + this.ToDoItems.push({id:uniqueId('todo-'), label: toDoLabel, done: false}); +}</pre> + </li> + <li> + <p>Try testing your form again, and you should see new to-do items get appended to the end of the list.</p> + </li> + <li> + <p>Let's make a further improvement before we move on. If you submit the form while the input is empy, todo items with no text still get added to the list. To fix that, we can prevent the todo-added event from firing when name is empty. Since name is already being trimmed by the <code>.trim</code> directive, we only need to test for the empty string.</p> + + <p>Go back to your <code>ToDoForm</code> component, and update the <code>onSubmit()</code> method like so. If the label value is empty, let's not emit the <code>todo-added</code> event.</p> + + <pre class="brush: js notranslate">onSubmit() { + if(this.label === "") { + return; + } + this.$emit('todo-added', this.label); +}</pre> + </li> + <li> + <p>Try your form again. Now you will not be able to add empty items to the to-do list.</p> + </li> +</ol> + +<p><img alt="Our todo list app rendered with a text input to enter new todos" src="https://mdn.mozillademos.org/files/17246/rendered-form-with-new-items.png" style="border-style: solid; border-width: 1px; display: block; height: 301px; margin: 0px auto; width: 442px;"></p> + +<h2 id="Using_v-model_to_update_an_input_value">Using <code>v-model</code> to update an input value</h2> + +<p>There's one more thing to fix in our <code>ToDoForm</code> component — after submitting, the <code><input></code> still contains the old value. But this is easy to fix — because we're using <code>v-model</code> to bind the data to the <code><input></code> in <code>ToDoForm</code>, if we set the name parameter to equal an empty string, the input will update as well.</p> + +<p>Update your <code>ToDoForm</code> component’s <code>onSubmit()</code> method to this:</p> + +<pre class="brush: js notranslate">onSubmit() { + if(this.label === "") { + return; + } + this.$emit('todo-added', this.label); + this.label = ""; +}</pre> + +<p>Now when you click the "Add" button, the "new-todo-input" will clear itself.</p> + +<h2 id="Summary">Summary</h2> + +<p>Excellent. We can now add todo items to our form! Our app is now starting to feel interactive, but one issue is that we've completely ignored its look and feel up to now. In the next article, we'll concentrate on fixing this, looking at the different ways Vue provides to style components.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_rendering_lists/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_rendering_lists/index.html new file mode 100644 index 0000000000..3f859f4c03 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_rendering_lists/index.html @@ -0,0 +1,227 @@ +--- +title: Rendering a list of Vue components +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists +tags: + - vue入门 +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">现在我们已经有了一个可以工作的组件。我们接下来添加更多的 <code>ToDoItem</code> 组件到 我们的App。 本文我们会添加一系列待办事项到App.vue组件并使用<code>v-for</code>指令遍历这些它们,将它们的每一项展示在<code>ToDoItem</code>组件中。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + + <p>Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with node + npm installed.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To learn how to loop through an array of data and render it in multiple components.</td> + </tr> + </tbody> +</table> + +<h2 id="利用_v-for_指令渲染列表">利用 v-for 指令渲染列表</h2> + +<p>一个有效的待办事项列表需要有多个被渲染的to-do项,Vue中的<code><a href="https://vuejs.org/v2/api/#v-for">v-fo</a>r</code> 可以用来实现这种效果。它是Vue自带的指令,用于在template中实现循环,我们可以利用它将数组中的各项重复渲染成指定的特征。我们将利用它迭代待办事项列表,将其中的每一项展示为单独的ToDoItem组件。</p> + +<h3 id="添加一些需要被渲染的数据">添加一些需要被渲染的数据</h3> + +<p>首先我们需要准备一个待办事项数组。添加 <code>data</code> 属性到 <code>App.vue</code> 组件对象中, 它包含一个 <code>ToDoItems</code> 字段,其值是待办事项数组。在最终完成添加新的待办事项功能之前,我们可以先mock一些待办项目,每个待办项目可以用一个对象表示,这个对象含有 <code>name</code> 和 <code>done</code> 属性。</p> + +<p>像下面这样添加一些待办项目让我们可以利用<code>v-for</code> 来对它们进行渲染。</p> + +<pre class="brush: js notranslate">export default { + name: 'app', + components: { + ToDoItem + }, + data() { + return { + ToDoItems: [ + { label: 'Learn Vue', done: false }, + { label: 'Create a Vue project with the CLI', done: true }, + { label: 'Have fun', done: true }, + { label: 'Create a to-do list', done: false } + ] + }; + } +};</pre> + +<p>现在我们有了一个列表,可以用<code>v-for</code>去展示它们了。指令的作用方式和元素的属性类似,就v-for而言,它类似js中的<code>for...in</code>循环,<code>v-for="item in items"</code> — <code>iterms</code>是你要迭代的列表, <code>item</code> 是数组中当前元素的引用。</p> + +<p><code>v-for</code>获取每个迭代的元素,并渲染它和它的子元素。在我们的例子中,我们用<code><li></code>的形式展示每一个待办事项,接下来我们会通过每个待办事项传递数据到其对应的<code>ToDoItem</code>组件。</p> + +<h3 id="Key_属性">Key 属性</h3> + +<p>在进行数据传递之前,我们要了解下<code>key</code>属性,它和<code>v-for</code>使用,用来帮助Vue标识列表中的元素,这样Vue就不需要在列表变化时重新创建它们。但是Vue需要一个唯一的标识,即<code>key</code>来识别哪些元素是被复用的。</p> + +<p>为了让Vue能正确的比较<code>key</code> ,key属性需要是numeric或者string类型。用name字段不是个好主意,因为这个字段会被用户输入控制,无法保证唯一性。</p> + +<p>我们可以使用<code>lodash.uniqueid()</code> ,像我们前一章节那样。</p> + +<ol> + <li> + <p>导入 <code>lodash.uniqueid</code> 到 <code>App</code> 组件。</p> + + <pre class="brush: js notranslate"> import uniqueId from 'lodash.uniqueid';</pre> + </li> + <li> + <p>添加 <code>id</code> 字段到 <code>ToDoItems</code> 数组的每一个元素中, 并且将他们赋值为 <code>uniqueId('todo-')。</code></p> + + <p><code>App.vue</code> <code><script></code> 元素内容如下:</p> + + <pre class="brush: js notranslate">import ToDoItem from './components/ToDoItem.vue'; +import uniqueId from 'lodash.uniqueid' + +export default { + name: 'app', + components: { + ToDoItem + }, + data() { + return { + ToDoItems: [ + { id: uniqueId('todo-'), label: 'Learn Vue', done: false }, + { id: uniqueId('todo-'), label: 'Create a Vue project with the CLI', done: true }, + { id: uniqueId('todo-'), label: 'Have fun', done: true }, + { id: uniqueId('todo-'), label: 'Create a to-do list', done: false } + ] + }; + } +};</pre> + </li> + <li> + <p>添加 <code>v-for</code> 指令和 <code>key</code> 属性到 <code><li></code> 元素:</p> + + <pre class="brush: html notranslate"><ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item label="My ToDo Item" :done="true"></to-do-item> + </li> +</ul></pre> + + <p>这样改后,<code><cli></code>标签中的js脚本就可以访问<code>item</code>了,这意味着我们可以使用<code>v-bind</code>来传递<code>item</code>对象的字段给<code>ToDoItem</code>组件了。这非常有用,我们想让列表中的待办事项的<code>label</code>值展示到它的label中,而不是显示一个静态的"My Todo Item"。此外,我们想让它们的checked状态反应它们的<code>done</code>字段,而不是默认的<code>done="false"</code>。</p> + </li> + <li> + <p>把 <code>label="My ToDo Item"</code> 改成 <code>:label="item.label"</code>, <code>:done="false"</code> 改成 <code>:done="item.done"</code> :</p> + </li> + <li> + <pre class="brush: html notranslate"><ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item :label="item.label" :done="item.done"></to-do-item> + </li> +</ul></pre> + </li> +</ol> + +<p>现在当你去看运行着的app时,你会发现待办事项显示了它们自己正确的名字,如果你查看源码的话,你会发现输入都有了唯一的id。<img alt="The app with a list of todo items rendered." src="https://mdn.mozillademos.org/files/17244/rendered-todo-items.png" style="border-style: solid; border-width: 1px; display: block; margin: 0 auto;"></p> + +<h2 id="让我们来一点小重构">让我们来一点小重构</h2> + +<p>我们可以做一点代码重构。 因为我们已经要为每一个待办事项创建一个唯一id,所以不妨把id作为ToDoItem的一个prop,而不是在每个checkbox里生成它。</p> + +<p>添加一个新的prop <code>id</code> 到 <code>ToDoItem</code> 组件。</p> + +<ol> + <li>标记它为required,类型是 <code>String</code> 。</li> + <li>为防止命名冲突,删除掉<code>data</code>属性中的<code>id</code>字段。</li> + <li>删除掉 <code>import uniqueId from 'lodash.uniqueid';</code> 这行。</li> +</ol> + +<p><code>ToDoItem</code> 中的 <code><script></code> 如下所示:</p> + +<pre class="brush: js notranslate">export default { + props: { + label: {required: true, type: String}, + done: {default: false, type: Boolean}, + id: {required: true, type: String} + }, + data() { + return { + isDone : this.done, + } + }, +}</pre> + +<p>现在,在 <code>App.vue</code> 组件中将 <code>item.id</code> 作为一个prop传递给 <code>ToDoItem</code> 组件。你的 <code>App.vue</code> template如下所示:</p> + +<pre class="brush: html notranslate"><template> + <div id="app"> + <h1>My To-Do List</h1> + <ul> + <li v-for="item in ToDoItems" :key="item.id"> + <to-do-item :label="item.label" :done="item.done" :id="item.id"></to-do-item> + </li> + </ul> + </div> +</template></pre> + +<p>你渲染后的站点看起来是没有变化的,但是这次重构使得<code>item.id</code>像其他参数一样,作为prop从<code>App.vue</code>传递给<code>ToDoItem</code>。现在代码变得更有逻辑性和一致。</p> + +<h2 id="总结">总结</h2> + +<p>我们现在有了样例数据,然后我们用循环将每一项渲染成<code>ToDoItem</code>。</p> + +<p>接下来我们需要让用户可以输入它们自己的待办事项,想做到这一点,我们需要一个文本输入<code><input></code>,当用户输入数据时触发一个事件,在事件响应函数中需要将数据添加到待办事项列表并且重新渲染列表,我们还需要一个模型操控数据。我们将在下一篇文章中获取这些知识。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">Getting started with Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">TypeScript support in Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_resources/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_resources/index.html new file mode 100644 index 0000000000..15152c2258 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_resources/index.html @@ -0,0 +1,117 @@ +--- +title: Vue resources +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">Now we'll round off our study of Vue by giving you a list of resources that you can use to go further in your learning, plus some other useful tips.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td> + <p>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages, knowledge of the <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">terminal/command line</a>.</p> + + <p>Vue components are written as a combination of JavaScript objects that manage the app's data and an HTML-based template syntax that maps to the underlying DOM structure. For installation, and to use some of the more advanced features of Vue (like Single File Components or render functions), you'll need a terminal with node + npm installed.</p> + </td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To learn where to go to find further information on Vue, to continue your learning.</td> + </tr> + </tbody> +</table> + +<h2 id="Further_resources">Further resources</h2> + +<p>这里可以学到更多Vue知识:</p> + +<ul> + <li><a href="https://vuejs.org/">Vue Docs</a> — The main Vue site. Contains comprehensive documentation, including examples, cookbooks, and reference material. This is the best place to start learning Vue in depth.</li> + <li><a href="https://github.com/vuejs/vue">Vue Github Repo</a> — The Vue code itself. This is where you can report issues and/or contribute directly to the Vue codebase. Studying the Vue source code can help you better understand how the framework works, and write better code.</li> + <li><a href="https://forum.vuejs.org/">Vue Forum</a> — 获取帮助的Vue官方论坛.</li> + <li><a href="https://cli.vuejs.org/">Vue CLI Docs</a> — Documentation for the Vue CLI. This contains information on customizing and extending the output you are generating via the CLI.</li> + <li><a href="https://nuxtjs.org/">NuxtJS</a> — NuxtJS is a Server-Side Vue Framework, with some architectural opinions that can be useful to creating maintainable applications, even if you don’t use any of the Server Side Rendering features it provides. This site provides detailed documentation on using NuxtJS.</li> + <li><a href="https://www.vuemastery.com/courses/">Vue Mastery</a> — 一个Vue学习平台, 也包含一些免费课程.</li> + <li><a href="https://vueschool.io/">Vue School</a> — 有一个Vue付费学习平台.</li> +</ul> + +<h2 id="构建并发布你的Vue_app">构建并发布你的Vue app</h2> + +<p>Vue CLI 也提供给我们的app准备发布到网络上的工具 .你可以这样做:</p> + +<ul> + <li>如果你的本地服务仍然正在运行, 在控制台按下<kbd>Ctrl</kbd> + <kbd>C</kbd> 停止它.</li> + <li>之后,在控制台运行 <code>npm run build</code> (或者 <code>yarn build</code>) .</li> +</ul> + +<p>这将创建一个新的<code>dist</code> 文件夹来包含你的准备生产的所有文件内容 .为了发布你的网站到网上 , 复制这个文件夹的所有内容到你的主机环境.</p> + +<div class="blockIndicator note"> +<p><strong>提示</strong>: The Vue CLI 文档也包含一个 <a href="https://cli.vuejs.org/guide/deployment.html#platform-guides">特别的指导来发布你的app</a> 到许多的公共主机平台 .</p> +</div> + +<h2 id="Vue_3">Vue 3</h2> + +<p>Vue 3 is a major release of the framework with a lot of major changes. It went into active beta in April, 2020. The biggest change is a new Composition API that works as an alternative to the current property-based API. In this new API, a single <code>setup()</code> function is used on the component. Only what you return from this function is available in your <code><template></code>s. You are required to be explicit about "reactive" properties when using this API. Vue handles this for you using the Options API. This makes the new API typically considered a more advanced use case.</p> + +<p>There are also a handful of other changes, including a change in how Apps are initialized in Vue. To read more about the changes involved with Vue 3, refer to <a href="https://vueschool.io/articles/vuejs-tutorials/exciting-new-features-in-vue-3/">this article by Vue School which goes over most of the major changes in Vue 3</a>.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">客户端框架介绍 </a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">开始使用 React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">开始我们的 React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">组件化我们的 React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React 资源</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">开始使用 Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">开始使用 Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">构建我们的第一个Vue组件</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">使用CSS装饰Vue组件 </a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">使用Vue计算属性</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue资源 </a></li> + </ul> + </li> + <li>Svelte + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started">开始使用Svelte</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning">Starting our Svelte Todo list app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props">Dynamic behavior in Svelte: working with variables and props</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components">Componentizing our Svelte app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility">Advanced Svelte: Reactivity, lifecycle, accessibility</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores">Working with Svelte stores</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript">Svelte中的 TypeScript 支持 </a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next">Deployment and next steps</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_styling/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_styling/index.html new file mode 100644 index 0000000000..114a2a15db --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/vue_styling/index.html @@ -0,0 +1,496 @@ +--- +title: Styling Vue components with CSS +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling +--- +<div><font><font>{{LearnSidebar}}</font></font></div> + +<div><font><font>{{PreviousMenuNext(“ Learn / Tools_and_testing / Client-side_JavaScript_frameworks / Vue_methods_events_models”,“ Learn / Tools_and_testing / Client-side_JavaScript_frameworks / Vue_computed_properties”,“ Learn / Tools_and_testing / Client-side_JavaScript_frameworks”)}</font></font></div> + +<p class="summary"><font><font>现在终于到了使我们的应用程序看起来更好的时候了。</font><font>在本文中,我们将探讨使用CSS样式Vue组件的不同方法。</font></font></p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row"><font><font>先决条件:</font></font></th> + <td> + <p><font><font>熟悉核心</font></font><a href="/en-US/docs/Learn/HTML"><font><font>HTML</font></font></a><font><font>,</font></font><a href="/en-US/docs/Learn/CSS"><font><font>CSS</font></font></a><font><font>和</font></font><a href="/en-US/docs/Learn/JavaScript"><font><font>JavaScript</font></font></a><font><font>语言,了解</font></font><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line"><font><font>终端/命令行</font></font></a><font><font>。</font></font></p> + + <p><font><font>Vue组件是由管理应用程序数据的JavaScript对象和映射到基础DOM结构的基于HTML的模板语法组成的。</font><font>为了进行安装并使用Vue的一些更高级的功能(例如“单个文件组件”或渲染功能),您将需要一个装有node + npm的终端。</font></font></p> + </td> + </tr> + <tr> + <th scope="row"><font><font>目的:</font></font></th> + <td><font><font>了解有关设置Vue组件样式的信息。</font></font></td> + </tr> + </tbody> +</table> + +<h2 id="使用CSS样式化Vue组件"><font><font>使用CSS样式化Vue组件</font></font></h2> + +<p><font><font>在继续向我们的应用程序添加更多高级功能之前,我们应该添加一些基本的CSS使其看起来更好。</font><font>Vue具有三种样式化应用程序的方法:</font></font></p> + +<ul> + <li><font><font>外部CSS文件。</font></font></li> + <li><font><font>单个文件组件(</font></font><code>.vue</code><font><font>文件)中的</font><font>全局样式</font><font>。</font></font></li> + <li><font><font>单个文件组件中组件范围的样式。</font></font></li> +</ul> + +<p><font><font>为帮助您熟悉每个应用程序,我们将所有三个功能结合使用,以使我们的应用程序具有更好的外观。</font></font></p> + +<h2 id="外部CSS文件的样式"><font><font>外部CSS文件的样式</font></font></h2> + +<p><font><font>您可以包括外部CSS文件,并将其全局应用于您的应用程序。</font><font>让我们看看这是如何完成的。</font></font></p> + +<p><font><font>首先,</font></font><code>reset.css</code><font><font>在</font></font><code>src/assets</code><font><font>目录中</font><font>创建一个名为</font><font>的</font><font>文件。</font><font>Webpack将处理此文件夹中的文件。</font><font>这意味着我们可以使用CSS预处理器(如SCSS)或后处理器(如PostCSS)。</font></font></p> + +<p><font><font>尽管本教程不会使用此类工具,但很高兴知道在资产文件夹中包含此类代码后,它将自动进行处理。</font></font></p> + +<p><font><font>将以下内容添加到</font></font><code>reset.css</code><font><font>文件中:</font></font></p> + +<pre class="brush: css notranslate">/*reset.css*/ +/* RESETS */ +*, +*::before, +*::after { + box-sizing: border-box; +} +*:focus { + outline: 3px dashed #228bec; +} +html { + font: 62.5% / 1.15 sans-serif; +} +h1, +h2 { + margin-bottom: 0; +} +ul { + list-style: none; + padding: 0; +} +button { + border: none; + margin: 0; + padding: 0; + width: auto; + overflow: visible; + background: transparent; + color: inherit; + font: inherit; + line-height: normal; + -webkit-font-smoothing: inherit; + -moz-osx-font-smoothing: inherit; + -webkit-appearance: none; +} +button::-moz-focus-inner { + border: 0; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0; +} +button, +input { + /* 1 */ + overflow: visible; +} +input[type="text"] { + border-radius: 0; +} +body { + width: 100%; + max-width: 68rem; + margin: 0 auto; + font: 1.6rem/1.25 "Helvetica Neue", Helvetica, Arial, sans-serif; + background-color: #f5f5f5; + color: #4d4d4d; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} +@media screen and (min-width: 620px) { + body { + font-size: 1.9rem; + line-height: 1.31579; + } +} +/*END RESETS*/</pre> + +<p><font><font>接下来,在您的</font></font><code>src/main.js</code><font><font>文件中,如下导入</font></font><code>reset.css</code><font><font>文件:</font></font></p> + +<pre class="brush: js notranslate">import './assets/reset.css';</pre> + +<p><font><font>这将导致在构建步骤中拾取文件并自动将其添加到我们的网站。</font></font></p> + +<p><font><font>重置样式应立即应用于该应用。</font><font>下图显示了应用重置前后应用程序的外观。</font></font></p> + +<p><font><font>之前:</font></font></p> + +<p><img alt="已添加部分样式的todo应用程序; 该应用现在位于卡片中,但某些内部功能仍需要样式" src="https://mdn.mozillademos.org/files/17247/todo-app-unstyled.png" style="border-style: solid; border-width: 1px; height: 763px; width: 1600px;"></p> + +<p><font><font>后:</font></font><img alt="已添加部分样式的todo应用程序; 该应用现在位于卡片中,但某些内部功能仍需要样式" src="https://mdn.mozillademos.org/files/17248/todo-app-reset-styles.png" style="border-style: solid; border-width: 1px; height: 929px; width: 1811px;"></p> + +<p><font><font>显着的更改包括删除列表项目符号,更改背景颜色以及更改基本按钮和输入样式。</font></font></p> + +<h2 id="向单个文件组件添加全局样式"><font><font>向单个文件组件添加全局样式</font></font></h2> + +<p><font><font>现在,我们已将CSS重置为在浏览器之间统一,我们需要对样式进行更多自定义。</font><font>我们希望将某些样式应用于应用程序中的各个组件。</font><font>虽然可以直接将这些文件添加到</font></font><code>reset.css</code><font><font>样式表中,但是我们将它们添加到的</font></font><code><style></code><font><font>标签中,</font></font><code>App.vue</code><font><font>以演示如何使用它们。</font></font></p> + +<p><font><font>文件中已经存在一些样式。</font><font>让我们删除它们,并用下面的样式替换它们。</font><font>这些样式可以做一些事情-为按钮和输入添加一些样式,并自定义</font></font><code>#app</code><font><font>元素及其子元素。</font></font></p> + +<p><font><font>更新</font></font><code>App.vue</code><font><font>文件的</font></font><code><style></code><font><font>元素,如下所示:</font></font></p> + +<pre class="brush: css notranslate"><style> +/* Global styles */ +.btn { + padding: 0.8rem 1rem 0.7rem; + border: 0.2rem solid #4d4d4d; + cursor: pointer; + text-transform: capitalize; +} +.btn__danger { + color: #fff; + background-color: #ca3c3c; + border-color: #bd2130; +} +.btn__filter { + border-color: lightgrey; +} +.btn__danger:focus { + outline-color: #c82333; +} +.btn__primary { + color: #fff; + background-color: #000; +} +.btn-group { + display: flex; + justify-content: space-between; +} +.btn-group > * { + flex: 1 1 auto; +} +.btn-group > * + * { + margin-left: 0.8rem; +} +.label-wrapper { + margin: 0; + flex: 0 0 100%; + text-align: center; +} +[class*="__lg"] { + display: inline-block; + width: 100%; + font-size: 1.9rem; +} +[class*="__lg"]:not(:last-child) { + margin-bottom: 1rem; +} +@media screen and (min-width: 620px) { + [class*="__lg"] { + font-size: 2.4rem; + } +} +.visually-hidden { + position: absolute; + height: 1px; + width: 1px; + overflow: hidden; + clip: rect(1px 1px 1px 1px); + clip: rect(1px, 1px, 1px, 1px); + clip-path: rect(1px, 1px, 1px, 1px); + white-space: nowrap; +} +[class*="stack"] > * { + margin-top: 0; + margin-bottom: 0; +} +.stack-small > * + * { + margin-top: 1.25rem; +} +.stack-large > * + * { + margin-top: 2.5rem; +} +@media screen and (min-width: 550px) { + .stack-small > * + * { + margin-top: 1.4rem; + } + .stack-large > * + * { + margin-top: 2.8rem; + } +} +/* End global styles */ +#app { + background: #fff; + margin: 2rem 0 4rem 0; + padding: 1rem; + padding-top: 0; + position: relative; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1); +} +@media screen and (min-width: 550px) { + #app { + padding: 4rem; + } +} +#app > * { + max-width: 50rem; + margin-left: auto; + margin-right: auto; +} +#app > form { + max-width: 100%; +} +#app h1 { + display: block; + min-width: 100%; + width: 100%; + text-align: center; + margin: 0; + margin-bottom: 1rem; +} +</style></pre> + +<p>If you check the app, you'll see that our todo list is now in a card, and we have some better formatting of our to-do items. Now we can go through and begin editing our components to use some of these styles.</p> + +<p><img alt="已添加部分样式的todo应用程序; 该应用现在位于卡片中,但某些内部功能仍需要样式" src="https://mdn.mozillademos.org/files/17249/todo-app-partial-styles.png" style="border-style: solid; border-width: 1px; height: 685px; width: 1398px;"></p> + +<h3 id="Adding_CSS_classes_in_Vue">Adding CSS classes in Vue</h3> + +<p>We should apply the button CSS classes to the <code><button></code> in our <code>ToDoForm</code> component. Since Vue templates are valid HTML, this is done in the same way to how you might do it in plain HTML — by adding a <code>class=""</code> attribute to the element.</p> + +<p>Add <code>class="btn btn__primary btn__lg"</code> to your form’s <code><button></code> element:</p> + +<pre class="brush: html notranslate"><button type="submit" class="btn btn__primary btn__lg"> + Add +</button></pre> + +<p>While we're here, there's one more semantic and styling change we can make. Since our form denotes a specific section of our page, it could benefit from an <code><h2></code> element. The label, however, already denotes the purpose of the form. To avoid repeating ourselves, let's wrap our label in an <code><h2></code>. There are a few other global CSS styles which we can add as well. We'll also add the <code>input__lg</code> class to our <code><input></code> element.</p> + +<p>Update your <code>ToDoForm</code> template so that it looks like this:</p> + +<pre class="brush: html notranslate"><template> + <form @submit.prevent="onSubmit"> + <h2 class="label-wrapper"> + <label for="new-todo-input" class="label__lg"> + What needs to be done? + </label> + </h2> + <input + type="text" + id="new-todo-input" + name="new-todo" + autocomplete="off" + v-model.lazy.trim="label" + class="input__lg" + /> + <button type="submit" class="btn btn__primary btn__lg"> + Add + </button> + </form> +</template></pre> + +<p>Let's also add the <code>stack-large</code> class to the <code><ul></code> tag in our <code>App.vue</code> file. This will help improve the spacing of our to-do items a bit.</p> + +<p>Update it as follows:</p> + +<pre class="brush: html notranslate"><ul aria-labelledby="list-summary" class="stack-large"></pre> + +<h2 id="Adding_scoped_styles">Adding scoped styles</h2> + +<p>The last component we want to style is our <code>ToDoItem</code> component. To keep the style definitions close to the component we can add a <code><style></code> element inside it. However, if these styles alter things outside of this component, it could be challenging to track down the styles responsible, and fix the problem. This is where the <code>scoped</code> attribute can be useful — this attaches a unique HTML <code>data</code> attribute selector to all of your styles, preventing them from colliding globally.</p> + +<p>To use the <code>scoped</code> modifier, create a <code><style></code> element inside <code>ToDoItem.vue</code>, at the bottom of the file, and give it a <code>scoped</code> attribute:</p> + +<pre class="brush: html notranslate"><style scoped> +</style></pre> + +<p>Next, copy the following CSS into the newly created <code><style></code> element:</p> + +<pre class="brush: css notranslate">.custom-checkbox > .checkbox-label { + font-family: Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 400; + font-size: 16px; + font-size: 1rem; + line-height: 1.25; + color: #0b0c0c; + display: block; + margin-bottom: 5px; +} +.custom-checkbox > .checkbox { + font-family: Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-weight: 400; + font-size: 16px; + font-size: 1rem; + line-height: 1.25; + box-sizing: border-box; + width: 100%; + height: 40px; + height: 2.5rem; + margin-top: 0; + padding: 5px; + border: 2px solid #0b0c0c; + border-radius: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} +.custom-checkbox > input:focus { + outline: 3px dashed #fd0; + outline-offset: 0; + box-shadow: inset 0 0 0 2px; +} +.custom-checkbox { + font-family: Arial, sans-serif; + -webkit-font-smoothing: antialiased; + font-weight: 400; + font-size: 1.6rem; + line-height: 1.25; + display: block; + position: relative; + min-height: 40px; + margin-bottom: 10px; + padding-left: 40px; + clear: left; +} +.custom-checkbox > input[type="checkbox"] { + -webkit-font-smoothing: antialiased; + cursor: pointer; + position: absolute; + z-index: 1; + top: -2px; + left: -2px; + width: 44px; + height: 44px; + margin: 0; + opacity: 0; +} +.custom-checkbox > .checkbox-label { + font-size: inherit; + font-family: inherit; + line-height: inherit; + display: inline-block; + margin-bottom: 0; + padding: 8px 15px 5px; + cursor: pointer; + touch-action: manipulation; +} +.custom-checkbox > label::before { + content: ""; + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + width: 40px; + height: 40px; + border: 2px solid currentColor; + background: transparent; +} +.custom-checkbox > input[type="checkbox"]:focus + label::before { + border-width: 4px; + outline: 3px dashed #228bec; +} +.custom-checkbox > label::after { + box-sizing: content-box; + content: ""; + position: absolute; + top: 11px; + left: 9px; + width: 18px; + height: 7px; + transform: rotate(-45deg); + border: solid; + border-width: 0 0 5px 5px; + border-top-color: transparent; + opacity: 0; + background: transparent; +} +.custom-checkbox > input[type="checkbox"]:checked + label::after { + opacity: 1; +} +@media only screen and (min-width: 40rem) { + label, + input, + .custom-checkbox { + font-size: 19px; + font-size: 1.9rem; + line-height: 1.31579; + } +}</pre> + +<p>Now we need to add some CSS classes to our template to connect the styles.</p> + +<p>To the root <code><div></code>, add a <code>custom-checkbox</code> class. To the <code><input></code>, add a <code>checkbox</code> class. Last of all, to the <code><label></code> add a <code>checkbox-label</code> class. The updated template is below:</p> + +<p>The app should now have custom checkboxes. Your app should look something like the screenshot below.</p> + +<p><img alt="具有完整样式的待办事项应用程序。 现在可以正确设置输入表单的样式,并且待办事项现在具有间距和自定义复选框" src="https://mdn.mozillademos.org/files/17250/todo-app-complete-styles.png" style="border-style: solid; border-width: 1px; height: 805px; width: 1600px;"></p> + +<h2 id="Summary">Summary</h2> + +<p>Our work is done on the styling of our sample app. In the next article we'll return to adding some more functionlity to our app, namely using a computed property to add a count of completed todo items to our app.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models","Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization"><font><font>Ember应用程序的结构和组件化</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state"><font><font>灰烬互动:事件,类和状态</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer"><font><font>灰烬交互性:页脚功能,条件渲染</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing"><font><font>在Ember中路由</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources"><font><font>灰烬资源和故障排除</font></font></a></li> + </ul> + </li> + <li><font><font>Vue</font></font> + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started"><font><font>Vue入门</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component"><font><font>创建我们的第一个Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists"><font><font>渲染Vue组件列表</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models"><font><font>添加新的待办事项表单:Vue事件,方法和模型</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling"><font><font>使用CSS样式化Vue组件</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties"><font><font>使用Vue计算的属性</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering"><font><font>Vue条件渲染:编辑现有待办事项</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management"><font><font>使用Vue裁判进行焦点管理</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources"><font><font>Vue资源</font></font></a></li> + </ul> + </li> + <li><font><font>斯维尔特</font></font> + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_getting_started"><font><font>Svelte入门</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning"><font><font>启动我们的Svelte Todo列表应用</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_variables_props"><font><font>Svelte中的动态行为:使用变量和道具</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_components"><font><font>组成我们的Svelte应用程序</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_reactivity_lifecycle_accessibility"><font><font>先进的Svelte:反应性,生命周期,可访问性</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_stores"><font><font>与Svelte商店合作</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_TypeScript"><font><font>Svelte中的TypeScript支持</font></font></a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_deployment_next"><font><font>部署和后续步骤</font></font></a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/介绍/index.html b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/介绍/index.html new file mode 100644 index 0000000000..3975354417 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/client-side_javascript_frameworks/介绍/index.html @@ -0,0 +1,393 @@ +--- +title: 客户端框架介绍 +slug: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/介绍 +tags: + - JavaScript + - 初学者 + - 学习 + - 客户端 + - 框架 +translation_of: Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</div> + +<p class="summary">在本章节我们开始大致了解框架, 简要回顾JavaScript和框架的历史,为什么框架会存在以及它们提供了什么, 如何开始考虑选择一个框架并学习, 以及对于客户端框架还有什么替代方案.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 以及 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言的核心.</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解客户端JavaScript框架是如何存在的,它们能解决什么问题,还有哪些替代方案,以及如何选择一个框架.</td> + </tr> + </tbody> +</table> + +<h2 id="简史">简史</h2> + +<p>当JavaScript在1996被发布后, 它给网络增加了少许的交互性和乐趣, 直到那时, 网页仍由静态文档组成. 网络应该不仅仅是阅读,更是创造的地方. 随着JavaScript的流行. 使用JavaScript的开发者们创造工具来解决他们遇到的问题, 并且将其打包成称为<strong>库</strong>的可复用组件, 这样就能和他人共享解决方案. 这种共享库的体系帮助塑造了网络的增长.</p> + +<p>现在, JavaScript是网络的基本部分, <a href="https://w3techs.com/technologies/details/cp-javascript">used on 95% of all websites</a>, 而且网络又是现代生活的基本部分. 用户在网络上写文章, 管理预算, 听音乐, 看电影, 以及和相隔万里的人通过文字, 音频, 视频聊天来瞬时交流. 网络让我们能够做到那些过去只能在电脑上安装本地应用程序才能做到的事. 这些现代的, 复杂的, 具有交互性的网站通常被称为 <strong>网络应用程序</strong>.</p> + +<p>现代JavaScript框架的到来加快了打造高度动态化和交互性强的应用程序的速度. <strong>框架</strong> 就是提供该如何构建应用程序的意见的库。这些意见能使应用具有可预测性和同质性。可预测性让软件能在扩展到很大规模的同时仍保持可维护性。可预测性和可维护性对于一个软件的长久健康运行是十分重要的。</p> + +<p>在现代网络中,JavaScript框架为众多令人印象深刻的软件提供支持——包括许多你可能每天都使用的网站。你正在阅读的这个MDN Web 文档页面, 就是使用React / ReactDOM框架为其前端提供动力。</p> + +<h2 id="有哪些框架">有哪些框架?</h2> + +<p>有很多框架可供你选择,但以下主要介绍目前公认的“四大框架”。</p> + +<h3 id="Ember">Ember</h3> + +<p><a href="https://emberjs.com/">Ember</a>于2011年12月发布,最初作为<a href="https://en.wikipedia.org/wiki/SproutCore">SproutCore</a>项目的延续而开始。比其新式的替代品(例如React和Vue),作为老框架它的用户人数要少得多。但因其稳定性、社区支持以及一些明智的编程原则,它仍然享有很高的知名度。</p> + +<h3 id="Angular">Angular</h3> + +<p><a href="https://angular.io">Angular </a>是一个开源 Web 应用程序框架,正式发布于2016年9月14日。它由构建<a href="https://angularjs.org/"> AngularJS</a> 的团队完全重写,并由 Google 的 Angular 团队以及个人和公司社区共同领导。</p> + +<p>Angular 是一种基于组件的框架,使用声明式的HTML模板。在应用构建时,框架的编译器将 HTML 模板转换为优化好的 JavaScript 指令,这一过程对开发者是透明的。Angular 使用 TypeScript,它是 JavaScript 的超集,我们将在下一章中对其进行更多介绍。</p> + +<h3 id="Vue">Vue</h3> + +<p>Evan You first released <a href="https://vuejs.org/">Vue</a> in 2014, after working on and learning from the original <a href="https://angularjs.org/">AngularJS</a> project. Vue is the youngest of the big four, but has enjoyed a recent uptick in popularity.</p> + +<p>Vue, like <a href="https://angularjs.org/">AngularJS</a>, extends HTML with some of its own code. Apart from that, it mainly relies on modern, standard JavaScript.</p> + +<h3 id="React">React</h3> + +<p>Facebook 在 2013 发布了 React。 在当时 React 已经被Facebook内部用来解决许多问题。 严格来说 React 本身并不是框架,而是一个用来渲染UI 组件的库。 React is used in combination with <em>other</em> libraries to make applications — React and <a href="https://reactnative.dev/">React Native</a> enable developers to make mobile applications; React and <a href="https://reactjs.org/docs/react-dom.html">ReactDOM</a> enables them to make web applications, etc.</p> + +<p>Because React and ReactDOM are so often used together, React is colloquially understood as a JavaScript framework. As you read through this module, we will be working with that colloquial understanding.</p> + +<p>React extends JavaScript with HTML-like syntax, known as <a href="https://reactjs.org/docs/introducing-jsx.html">JSX</a>.</p> + +<h2 id="框架为何会存在">框架为何会存在?</h2> + +<p>我们已经讨论了因为什么契机而创造了框架,但我们仍不知道为什么开发者认为有必要创造它。要知道这个问题的答案,我们首先需要检查软件开发中的各种挑战。</p> + +<p>设想一个很常见的软件:一个To-Do清单创建器,在接下来的章节中我们会使用各种框架来实现它。这个应用应让用户可以完成诸如呈现任务列表、添加和删除任务等操作,且在完成这些操作的同时能可靠地跟踪和更新应用程序的底层数据。在软件开发中,这种底层数据被称为状态。</p> + +<p>上述每个目标理论上都很简单。我们可以遍历数据来列出清单,添加一个对象来创建新任务,使用标识符来查找、编辑和删除任务。需要注意的是,用户都是在浏览器中使用应用的这些功能,然而这就引出了一些问题: <strong>每当我们修改应用的数据时,我们都需要更新用户界面以使其匹配。</strong> </p> + +<p>我们可以通过To-Do应用的一个功能来检验这个问题的难点:呈现任务清单。</p> + +<h2 id="冗长的DOM操作">冗长的DOM操作</h2> + +<p>Building HTML elements and rendering them in the browser at the appropriate time takes a surprising amount of code. Let's say that our state is an array of objects structured like this:</p> + +<pre class="brush: js notranslate">const state = [ + { + id: 'todo-0', + name: 'Learn some frameworks!' + } +]</pre> + +<p>How do we show one of those tasks to our user? We want to represent each task as a list item – an HTML <code><a href="/en-US/docs/Web/HTML/Element/li"><li></a></code> element inside of an unordered list element (a <code><a href="/en-US/docs/Web/HTML/Element/ul"><ul></a></code>). How do we make it? That could look something like this:</p> + +<pre class="brush: js notranslate">function buildTodoItemEl(id, name) { + const item = document.createElement('li'); + const span = document.createElement('span'); + const textContent = document.createTextNode(name); + + span.appendChild(textContent) + + item.id = id; + item.appendChild(span); + item.appendChild(buildDeleteButtonEl(id)); + + return item; +}</pre> + +<p>Here, we use the <code><a href="/en-US/docs/Web/API/Document/createElement">document.createElement()</a></code> method to make our <code><li></code>, and several more lines of code to create the properties and children it needs.</p> + +<p>The tenth line of this snippet references another build function: <code>buildDeleteButtonEl()</code>. It follows a similar pattern to the one we used to build a list item element:</p> + +<pre class="brush: js notranslate">function buildDeleteButtonEl(id) { + const button = document.createElement('button'); + const textContent = document.createTextNode('Delete'); + + button.setAttribute('type', 'button'); + button.appendChild(textContent); + + return button; +}</pre> + +<p>This button doesn't do anything yet, but it will later once we decide to implement our delete feature. The code that will render our items on the page might read something like this:</p> + +<pre class="brush: js notranslate">function renderTodoList() { + const frag = document.createDocumentFragment(); + state.tasks.forEach(task => { + const item = buildTodoItemEl(task.id, task.name); + frag.appendChild(item); + }); + + while (todoListEl.firstChild) { + todoListEl.removeChild(todoListEl.firstChild); + } + todoListEl.appendChild(frag); +}</pre> + +<p>We've now got well over thirty lines of code dedicated <em>just</em> to the UI – <em>just</em> to the step of rendering something in the DOM – and at no point do we add classes that we could use later to style our list-items!</p> + +<p>Working directly with the DOM, as in this example, requires understanding many things about how the DOM works: how to make elements; how to change their properties; how to put elements inside of each other; how to get them on the page. None of this code actually handles user interactions, or addresses adding or deleting a task. If we add those features, we have to remember to update our UI in the right time and in the right way.</p> + +<p>JavaScript frameworks were created to make this kind of work a little easier — they exist to provide a better <em>developer experience</em>. They don't bring brand-new powers to JavaScript; they give you easier access to JavaScript's powers so you can build for today's web.</p> + +<p>If you want to see code samples from this section in action, you can check out a <a href="https://codepen.io/dengeist/pen/XWbPNmw">working version of the app on CodePen</a>, which also allows users to add and delete new tasks.</p> + +<p>Read more about the JavaScript used in this section:</p> + +<ul> + <li><code><a href="/en-US/docs/Web/API/Document/createElement">document.createElement()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Document/createTextNode">document.createTextNode()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Document/createDocumentFragment">document.createDocumentFragment()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">eventTarget.addEventListener()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Node/appendChild">node.appendChild()</a></code></li> + <li><code><a href="/en-US/docs/Web/API/Node/removeChild">node.removeChild()</a></code></li> +</ul> + +<h2 id="另一种打造UIs的方式">另一种打造UIs的方式</h2> + +<p>Every JavaScript framework offers a way to write user interfaces more <em>declaratively</em>. That is, they allow you to write code that describes how your UI should look, and the framework makes it happen in the DOM behind the scenes.</p> + +<p>The vanilla JavaScript approach to building out new DOM elements in repetition was difficult to understand at a glance. By contrast, the following block of code illustrates the way you might use Vue to describe our list of tasks:</p> + +<pre class="brush: html notranslate"><ul> + <li v-for="task in tasks" v-bind:key="task.id"> + <span>\{{task.name\}}</span> + <button type="button">Delete</button> + </li> +</ul></pre> + +<p>That's it. This snippet reduces approximately thirty-two lines of code down to six lines. If the curly braces and <code>v-</code> attributes here are unfamiliar to you, that's okay; you’ll learn about Vue-specific syntax later on in the module. The thing to take away here is that this code looks like the UI it represents, whereas the vanilla JavaScript code does not.</p> + +<p>Thanks to Vue, we didn't have to write our own functions for building the UI; the framework will handle that for us in an optimized, efficient way. Our only role here was to describe to Vue what each item should look like. Developers who are familiar with Vue can join our project and quickly work out what is going on. Vue is not alone in this: using a framework improves team as well as individual efficiency.</p> + +<p>It's possible to do things <em>similar</em> to this in vanilla JavaScript. <a href="/en-US/docs/Web/JavaScript/Reference/Template_literals">Template literal strings</a> make it easy to write strings of HTML that represent what the final element would look like. That might be a useful idea for something as simple as our to-do list application, but it's not maintainable for large applications that manage thousands of records of data, and could render just as many unique elements in a user interface.</p> + +<h2 id="框架提供给我们的其他功能">框架提供给我们的其他功能</h2> + +<p>Let's look at some of the other advantages conferred upon us by frameworks. As we've alluded to before, the advantages of frameworks are achievable in vanilla JavaScript, but using a framework takes away all of the cognitive load of having to solve these problems yourself.</p> + +<h3 id="Tooling">Tooling</h3> + +<p>Because each of the frameworks in this module have a large, active community, each framework's ecosystem provides tooling that Improves the developer experience. These tools make it easy to add things like testing (to ensure that your application behaves as it should) or linting (to ensure that your code is error-free and stylistically consistent).</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: If you want to find out more details about web tooling concepts, have a read of our <a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">Client-side tooling overview</a>.</p> +</div> + +<h3 id="Compartmentalization">Compartmentalization</h3> + +<p>Most major frameworks encourage developers to abstract the different parts of their user interfaces into <em>components</em> — maintainable, reusable chunks of code that can communicate with one another. All the code related to a given component can live in one file (or a couple of specific files), so that you as a developer know exactly where to go to make changes to that component. In a vanilla JavaScript app, you'd have to create your own set of conventions to achieve this in an efficient, scalable way. Many JavaScript developers, if left to their own devices, could end up with all the code related to one part of the UI being spread out all over a file — or in another file altogether.</p> + +<h3 id="Routing">Routing</h3> + +<p>The most essential feature of the web is that it allows users to navigate from one page to another – it is, after all, a network of interlinked documents. When you follow a link on this very website, your browser communicates with a server and fetches new content to display for you. As it does so, the URL in your address bar changes. You can save this new URL and come back to the page later on, or share it with others so they can easily find the same page. Your browser remembers your navigation history and allows you to navigate back and forth, too. This is called <strong>server-side routing</strong>.</p> + +<p>Modern web applications typically do not fetch and render new HTML files — they load a single HTML shell, and continually update the DOM inside it (referred to as <strong>single page apps</strong>, or <strong>SPAs</strong>) without navigating users to new addresses on the web. Each new pseudo-webpage is usually called a <em>view</em>, and by default, no routing is done.</p> + +<p>When an SPA is complex enough, and renders enough unique views, it's important to bring routing functionality into your application. People are used to being able to link to specific pages in an application, travel forward and backward in their navigation history, etc., and their experience suffers when these standard web features are broken. When routing is handled by a client application in this fashion, it is aptly called <strong>client-side routing</strong>.</p> + +<p>It's <em>possible</em> to make a router using the native capabilities of JavaScript and the browser, but popular, actively developed frameworks have companion libraries that make routing a more intuitive part of the development process.</p> + +<h2 id="使用框架的注意事项">使用框架的注意事项</h2> + +<p>Being an effective web developer means using the most appropriate tools for the job. JavaScript frameworks make front-end application development easy, but they are not a silver bullet that will solve all problems. This section talks about some of the things you should consider when using frameworks. Bear in mind that you might not need a framework at all — beware that you don't end up using a framework just for the sake of it.</p> + +<h3 id="Familiarity_with_the_tool">Familiarity with the tool</h3> + +<p>Just like vanilla JavaScript, frameworks take time to learn and have their quirks. Before you decide to use a framework for a project, be sure you have time to learn enough of its features for it to be useful to you rather than it working against you, and be sure that your teammates are comfortable with it as well.</p> + +<h3 id="Overengineering">Overengineering</h3> + +<p>If your web development project is a personal portfolio with a few pages, and those pages have little or no interactive capability, a framework (and all of its JavaScript) may not be necessary at all. That said, frameworks are not a monolith, and some of them are better-suited to small projects than others. In an article for Smashing Magazine, Sarah Drasner writes about how <a href="https://www.smashingmagazine.com/2018/02/jquery-vue-javascript/">Vue can replace jQuery</a> as a tool for making small portions of a webpage interactive.</p> + +<h3 id="Larger_code_base_and_abstraction">Larger code base and abstraction</h3> + +<p>Frameworks allow you to write more declarative code – and sometimes <em>less</em> code overall – by dealing with the DOM interactions for you, behind the scenes. This abstraction is great for your experience as a developer, but it isn't free. In order to translate what you write into DOM changes, frameworks have to run their own code, which in turn makes your final piece of software larger and more computationally expensive to operate.</p> + +<p>Some extra code is inevitable, and a framework that supports tree-shaking (removal of any code that isn't actually used in the app during the build process) will allow you to keep your applications small, but this is still a factor you need to keep in mind when considering your app's performance, especially on more network/storage-constrained devices, like mobile phones.</p> + +<p>The abstraction of frameworks affects not only your JavaScript, but your relationship with the very nature of the web. No matter how you build for the web, the end result, the layer that your users ultimately interact with, is HTML. Writing your whole application in JavaScript can make you lose sight of HTML and the purpose of its various tags, and lead you to produce an HTML document that is un-semantic and inaccessible. In fact, it's possible to write a fragile application that depends entirely on JavaScript and will not function without it.</p> + +<p>Frameworks are not the source of our problems. With the wrong priorities, it's possible for <em>any</em> application to be fragile, bloated, and inaccessible. Frameworks do, however, amplify our priorities as developers. If your priority is to make a complex web app, it's easy to do that. However, if your priorities don't carefully guard performance and accessibility, frameworks will amplify your fragility, your bloat, and your inaccessibility. Modern developer priorities, amplified by frameworks, have inverted the structure of the web in many places. Instead of a robust, content-first network of documents, the web now often puts JavaScript first and user experience last.</p> + +<h2 id="框架驱动页面的可访问性">框架驱动页面的可访问性</h2> + +<p>Let's build on what we said in the previous section, and talk a bit more about accessibility. Making user interfaces accessible always requires some thought and effort, and frameworks can complicate that process. You often have to employ advanced framework APIs to access native browser features like ARIA <a href="/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions">live regions</a> or focus management.</p> + +<p>In some cases, framework applications create accessibility barriers that do not exist for traditional websites. The biggest example of this is in client-side routing, as mentioned earlier.</p> + +<p>With traditional (server-side) routing, navigating the web has predictable results. The browser knows to set focus to the top of the page and assistive technologies will announce the title of the page. These things happen every time you navigate to a new page.</p> + +<p>With client-side routing, your browser is not loading new web pages, so it doesn't know that it should automatically adjust focus or announce a new page title. Framework authors have devoted immense time and labor to writing JavaScript that recreates these features, and even then, no framework has done so perfectly.</p> + +<p>The upshot is that you should consider accessibility from the very start of <em>every</em> web project, but bear in mind that abstracted codebases that use frameworks are more likely to suffer from major accessibility issues if you don't.</p> + +<h2 id="如何选择一个框架">如何选择一个框架</h2> + +<p>Each of the frameworks discussed in this module take different approaches to web application development. Each is regularly improving or changing, and each has its pros and cons. Choosing the right framework is a team- and project-dependent process, and you should do your own research to uncover what suits your needs. That said, we've identified a few questions you can ask in order to research your options more effectively:</p> + +<ol> + <li>What browsers does the framework support?</li> + <li>What domain-specific languages does the framework utilize?</li> + <li>Does the framework have a strong community and good docs (and other support) available?</li> +</ol> + +<p>The table in this section provides a glanceable summary of the current <em>browser support</em> offered by each framework, as well as the <strong>domain-specific languages</strong> with which it can be used.</p> + +<p>Broadly, domain-specific languages (<strong>DSLs</strong>) are programming languages relevant in specific areas of software development. In the context of frameworks, DSLs are variations on JavaScript or HTML that make it easier to develop with that framework. Crucially, none of the frameworks <em>require</em> a developer to use a specific DSL, but they have almost all been designed with a specific DSL in mind. Choosing not to employ a framework’s preferred DSL will mean you miss out on features that would otherwise improve your developer experience.</p> + +<p>You should seriously consider the support matrix and DSLs of a framework when making a choice for any new project. Mismatched browser support can be a barrier to your users; mismatched DSL support can be a barrier to you and your teammates.</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col">Framework</th> + <th scope="col">Browser support</th> + <th scope="col">Preferred DSL</th> + <th scope="col">Supported DSLs</th> + </tr> + </thead> + <tbody> + <tr> + <td>Angular</td> + <td>IE9+</td> + <td>TypeScript</td> + <td>HTML-based; TypeScript</td> + </tr> + <tr> + <td>React</td> + <td>Modern (IE9+ with Polyfills)</td> + <td>JSX</td> + <td>JSX; TypeScript</td> + </tr> + <tr> + <td>Vue</td> + <td>IE9+</td> + <td>HTML-based</td> + <td>HTML-based, JSX, Pug</td> + </tr> + <tr> + <td>Ember</td> + <td>Modern (IE9+ in Ember version 2.18)</td> + <td>Handlebars</td> + <td>Handlebars, TypeScript</td> + </tr> + </tbody> +</table> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: DSLs we've described as "HTML-based" do not have official names. They are not really true DSLs, but they are non-standard HTML, so we believe they are worth highlighting.</p> +</div> + +<p>Citations for this table:</p> + +<ul> + <li><a href="https://reactjs.org/docs/react-dom.html#browser-support">React browser support: official docs</a></li> + <li><a href="https://blog.emberjs.com/2018/02/14/ember-3-0-released.html">Ember browser support: Ember 3.0 release announcement</a></li> + <li><a href="https://guides.emberjs.com/v3.3.0/templates/handlebars-basics/">Ember templating language (official docs)</a></li> +</ul> + +<h3 id="Does_the_framework_have_a_strong_community">Does the framework have a strong community?</h3> + +<p>This is perhaps the hardest metric to measure, because community size does not correlate directly to easy-to-access numbers. You can check a project's number of GitHub stars or weekly npm downloads to get an idea of its popularity, but sometimes the best thing to do is search a few forums or talk to other developers. It is not just about the community's size, but also how welcoming and inclusive it is, and how good available documentation is.</p> + +<h3 id="Opinions_on_the_web">Opinions on the web</h3> + +<p>Don't just take our word on this matter — there are discussions all over the web. The Wikimedia Foundation recently chose to use Vue for its front-end, and posted a <a href="https://phabricator.wikimedia.org/T241180">request for comments (RFC) on framework adoption</a>. Eric Gardner, the author of the RFC, took time to outline the needs of the Wikimedia project and why certain frameworks were good choices for the team. This RFC serves as a great example of the kind of research you should do for yourself when planning to use a front-end framework.</p> + +<p>The <a href="https://stateofjs.com/">State of JavaScript survey</a> is a helpful collection of feedback from JavaScript developers. It covers many topics related to JavaScript, including data about both the use of frameworks and developer sentiment toward them. Currently, there are several years of data available, allowing you to get a sense of a framework's popularity.</p> + +<p>The Vue team has <a href="https://vuejs.org/v2/guide/comparison.html">exhaustively compared Vue to other popular frameworks</a>. There may be some bias in this comparison (which they note), but it's a valuable resource nonetheless.</p> + +<h2 id="客户端框架的替代方案">客户端框架的替代方案</h2> + +<p>If you’re looking for tools to expedite the web development process, and you know your project isn’t going to require intensive client-side JavaScript, you could reach for one of a handful of other solutions for building the web:</p> + +<ul> + <li>A content management system</li> + <li>Server-side rendering</li> + <li>A static site generator</li> +</ul> + +<h3 id="Content_management_systems">Content management systems</h3> + +<p><strong>Content-management systems</strong> (<strong>CMSes</strong>) are any tools that allow a user to create content for the web without directly writing code themselves. They're a good solution for large projects, especially projects that require input from content writers who have limited coding ability, or for programmers who want to save time. They do, however, require a significant amount of time to set up, and utilizing a CMS means that you surrender at least some measure of control over the final output of your website. For example: if your chosen CMS doesn't author accessible content by default, it's often difficult to improve this.</p> + +<p>Popular examples include <a href="https://wordpress.com/">Wordpress</a>, <a href="https://www.joomla.org/">Joomla</a>, and <a href="https://www.drupal.org/">Drupal</a>.</p> + +<h3 id="Server-side_rendering">Server-side rendering</h3> + +<p><strong>Server-side rendering</strong> (<strong>SSR</strong>) is an application architecture in which it is the <em>server'</em>s job to render a single-page application. This is the opposite of <em>client-side rendering</em>, which is the most common and most straightforward way to build a JavaScript application. Server-side rendering is easier on the client's device, because you're only sending a rendered HTML file to them, but it can be difficult to set up compared to a client-side-rendered application.</p> + +<p>All of the frameworks covered in this module support server-side rendering as well as client-side rendering. Check out <a href="https://nextjs.org/">Next.js</a> for React, <a href="https://nuxtjs.org/">Nuxt.js</a> for Vue (yes it is confusing, and no, these projects are not related!), <a href="https://github.com/ember-fastboot/ember-cli-fastboot">FastBoot</a> for Ember, and <a href="https://angular.io/guide/universal">Angular Universal</a> for Angular.</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Some SSR solutions are written and maintained by the community, whereas some are "official" solutions provided by the framework's maintainer.</p> +</div> + +<h3 id="Static_site_generators">Static site generators</h3> + +<p>Static site generators are programs that dynamically generate all the webpages of a multi-page website — including any relevant CSS or JavaScript — so that they can be published in any number of places. The publishing host could be a GitHub pages branch, a Netlify instance, or any private server of your choosing, for example. There are a number of advantages of this approach, mostly around performance (your user's device isn’t building the page with JavaScript; it's already complete) and security (static pages have fewer attack vectors). These sites can still utilize JavaScript where they need to, but they are not <em>dependent</em> upon it. Static site generators take time to learn, just like any other tool, which can be a barrier to your development process.</p> + +<p>Static sites can have as few or as many unique pages as you want. Just as frameworks empower you to quickly write client-side JavaScript applications, static site generators allow you a way to quickly create HTML files you would otherwise have written individually. Like frameworks, static site generators allow developers to write components that define common pieces of your web pages, and to compose those components together to create a final page. In the context of static site generators, these components are called <strong>templates</strong>. Web pages built by static site generators can even be home to framework applications: if you want one specific page of your statically-generated website to boot up a React application when your user visits it for example, you can do that.</p> + +<p>Static site generators have been around for quite a long time, but they have seen a bit of a revival in the recent history of the web. A handful of powerful options are now available, such as <a href="https://gohugo.io/">Hugo</a>, <a href="https://jekyllrb.com/">Jekyll</a>, <a href="https://www.11ty.dev/">Eleventy</a>, and <a href="https://www.gatsbyjs.org/">Gatsby</a>.</p> + +<p>If you'd like to learn more about static site generators on the whole, check out Tatiana Mac's <a href="https://tatianamac.com/posts/beginner-eleventy-tutorial-parti/">Beginner's guide to Eleventy</a>. In the first article of the series, she explains what a static site generator is, and how it relates to other means of publishing web content.</p> + +<h2 id="总结">总结</h2> + +<p>And that brings us to the end of our introduction to frameworks — we’ve not taught you any code yet, but hopefully we've given you a useful background on why you'd use frameworks in the first place and how to go about choosing one, and made you excited to learn more and get stuck in!</p> + +<p>Our next article goes down to a lower level, looking at the specific kinds of features frameworks tend to offer, and why they work like they do.</p> + +<p>{{NextMenu("Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features", "Learn/Tools_and_testing/Client-side_JavaScript_frameworks")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Introduction">Introduction to client-side frameworks</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Main_features">Framework main features</a></li> + <li>React + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_getting_started">Getting started with React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning">Beginning our React todo list</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_components">Componentizing our React app</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_events_state">React interactivity: Events and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_interactivity_filtering_conditional_rendering">React interactivity: Editing, filtering, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_accessibility">Accessibility in React</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_resources">React resources</a></li> + </ul> + </li> + <li>Ember + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_getting_started">Getting started with Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_structure_componentization">Ember app structure and componentization</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_interactivity_events_state">Ember interactivity: Events, classes and state</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_conditional_footer">Ember Interactivity: Footer functionality, conditional rendering</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_routing">Routing in Ember</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Ember_resources">Ember resources and troubleshooting</a></li> + </ul> + </li> + <li>Vue + <ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started">Getting started with Vue</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_first_component">Creating our first Vue component</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_rendering_lists">Rendering a list of Vue components</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_methods_events_models">Adding a new todo form: Vue events, methods, and models</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_styling">Styling Vue components with CSS</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_computed_properties">Using Vue computed properties</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_conditional_rendering">Vue conditional rendering: editing existing todos</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_refs_focus_management">Focus management with Vue refs</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_resources">Vue resources</a></li> + </ul> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/automated_testing/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/automated_testing/index.html new file mode 100644 index 0000000000..76dbbef3b8 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/automated_testing/index.html @@ -0,0 +1,602 @@ +--- +title: Introduction to automated testing +slug: Learn/Tools_and_testing/Cross_browser_testing/Automated_testing +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Automated_testing +--- +<div> {{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">Manually running tests on several browsers and devices, several times per day, can get tedious, and time consuming. To handle this efficiently, you should become familiar with automation tools. In this article, we look at what is available, how to use task runners, and how to use the basics of commercial browser test automation apps such as, Sauce Labs and Browser Stack.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages; an idea of the high level <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">principles of cross browser testing</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To provide an understanding of what automated testing entails, how it can make your life easier, and how to make use of some of the commercial products that make things easier.</td> + </tr> + </tbody> +</table> + +<h2 id="自动化让事情变得简单">自动化让事情变得简单</h2> + +<p>Throughout this module we have detailed loads of different ways in which you can test your websites and apps, and explained the sort of scope your cross-browser testing efforts should have in terms of what browsers to test, accessibility considerations, and more. Sounds like a lot of work, doesn't it?</p> + +<p>We agree — testing all the things we've looked at in previous articles manually can be a real pain. Fortunately, there are tools to help us automate some of this pain away. There are two main ways in which we can automate the tests we've been talking about in this module:</p> + +<ol> + <li>Use a task runner such as <a href="http://gruntjs.com/">Grunt</a> or <a href="http://gulpjs.com/">Gulp</a>, or <a href="https://docs.npmjs.com/misc/scripts">npm scripts</a> to run tests and clean up code during your build process. This is a great way to perform tasks like linting and minifying code, adding in CSS prefixes or transpiling nascent JavaScript features for maximum cross-browser reach, and so on.</li> + <li>Use a browser automation system like <a href="http://www.seleniumhq.org/">Selenium</a> to run specific tests on installed browsers and return results, alerting you to failures in browsers as they crop up. Commercial cross-browser testing apps like <a href="https://saucelabs.com/">Sauce Labs</a> and <a href="https://www.browserstack.com/">Browser Stack</a> are based on Selenium, but allow you to access their set up remotely using a simple interface, saving you the hassle of setting up your own testing system.</li> +</ol> + +<p>We will look at how to set up your own Selenium-based testing system in the next article. In this article, we'll look at how to set up a task runner, and use the basic functionality of commercial systems like the ones mentioned above.</p> + +<div class="note"> +<p><strong>Note</strong>: the above two categories are not mutually exclusive. It is possible to set up a task runner to access a service like Sauce Labs via an API, run cross browser tests, and return results. We will look at this below as well.</p> +</div> + +<h2 id="使用任务运行器作为自动化测试工具">使用任务运行器作为自动化测试工具</h2> + +<p>As we said above, you can drastically speed up common tasks such as linting and minifying code by using a task runner to run everything you need to run automatically at a certain point in your build process. For example, this could be every time you save a file, or at some other point. Inside this section we'll look at how to automate task running with Node and Gulp, a beginner-friendly option.</p> + +<h3 id="Setting_up_Node_and_npm">Setting up Node and npm</h3> + +<p>很多工具都是基于 {{Glossary("Node.js")}},所以你需要从 <a href="https://nodejs.org/">nodejs.org</a>安装它:</p> + +<ol> + <li>从上面的网站下载适合你系统的安装软件。 (如果你已经安装了Node和npm,跳到步骤4)</li> + <li>像其它软件那样正常安装。注意Node来源于 <a href="https://www.npmjs.com/">Node Package Manager</a> (npm),它使你能更快地安装包、同它人分享你的包,并在你的工程上运行有用的脚本。</li> + <li>安装结束后,运行下列命令查看Node和npm的版本,检测node是否正确安装好 : + <pre class="brush: bash">node -v +npm -v</pre> + </li> + <li>安装 Node/npm 后,还应将其更新到最新的版本。 更新Node最有效可靠的方式是从它的网站(链接见上)下载一个最新的安装包进行安装。 而更新npm,可以在你的命令行终端运行如下命令: + <pre class="brush: bash">npm install npm@latest -g</pre> + </li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: If the above command fails with permissions errors, <a href="https://docs.npmjs.com/getting-started/fixing-npm-permissions">Fixing npm permissions</a> should sort you out.</p> +</div> + +<p>要在你的工程上使用基于Node/npm的包,你还需要把你的工程目录设为npm工程。这并不难。</p> + +<p>举例如下,首先创建一个测试目录,以免在测试的过程中产生任何错误。</p> + +<ol> + <li>使用文件管理UI在合适的地方创建一个新的目录,或在命令行下定位到你想要创建目录的地方,执行如下命令: + <pre class="brush: bash">mkdir node-test</pre> + </li> + <li>要使这个目录成为一个npm工程, 你需要进入目录下然后初始化它: + <pre class="brush: bash">cd node-test +npm init</pre> + </li> + <li>第2步执行后你会看到要回答一些问题,这是建立工程需要获取的信息。现在你可以先选择使用默认值。</li> + <li>所有问题回答完成后,你还需要确定信息是否OK。输入yes或者直接回车,npm就会在你的目录下生成一个package.json文件。</li> +</ol> + +<p>这是一个工程的基本配置文件。你可以后续对它进行修改,现在,它的内容是这样的:</p> + +<pre class="brush: json">{ + "name": "node-test", + "version": "1.0.0", + "description": "Test for npm projects", + "main": "index.js", + "scripts": { + "test": "test" + }, + "author": "Chris Mills", + "license": "MIT" +}</pre> + +<p>现在,你可以继续往下了。</p> + +<h3 id="Setting_up_Gulp_automation">Setting up Gulp automation</h3> + +<p>Let's look at setting up Gulp and using it to automate some testing tools.</p> + +<ol> + <li>To begin with, create a test npm project using the procedure detailed at the bottom of the previous section.</li> + <li>Next, you'll need some sample HTML, CSS and JavaScript content to test your system on — make copies of our sample <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/automation/index.html">index.html</a>, <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/automation/main.js">main.js</a>, and <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/automation/style.css">style.css</a> files in a subfolder with the name <code>src</code> inside your project folder. You can try your own test content if you like, but bear in mind that such tools won't work on internal JS/CSS — you need external files.</li> + <li>First, install gulp globally (meaning, it will be available across all projects) using the following command: + <pre class="brush: bash">npm install --global gulp-cli</pre> + </li> + <li>Next, run the following command inside your npm project directory root to set up gulp as a dependency of your project: + <pre class="brush: bash">npm install --save-dev gulp</pre> + </li> + <li>Now create a new file inside your project directory called <code>gulpfile.js</code>. This is the file that will run all our tasks. Inside this file, put the following: + <pre class="brush: js">var gulp = require('gulp'); + +gulp.task('default', function() { + console.log('Gulp running'); +});</pre> + This requires the <code>gulp</code> module we installed earlier, and then runs a basic task that does nothing except for printing a message to the terminal — this is useful for letting us know that Gulp is working. Each gulp task is written in the same basic format — <code>gulp</code>'s <code>task()</code> method is run, and given two parameters — the name of the task, and a callback function containing the actual code to run to complete the task.</li> + <li>You can run your gulp task with the following commands — try this now: + <pre class="brush: bash">gulp +</pre> + </li> +</ol> + +<h3 id="Adding_some_real_tasks_to_Gulp">Adding some real tasks to Gulp</h3> + +<p>To add some real tasks to Gulp, we need to think about what we want to do. A reasonable set of basic functionalities to run on our project is as follows:</p> + +<ul> + <li>html-tidy, css-lint, and js-hint to lint and report/fix common HTML/CSS/JS errors (see <a href="https://www.npmjs.com/package/gulp-htmltidy/">gulp-htmltidy</a>, <a href="https://www.npmjs.com/package/gulp-csslint/">gulp-csslint</a>, <a href="https://www.npmjs.com/package/gulp-jshint/">gulp-jshint</a>).</li> + <li>Autoprefixer to scan our CSS and add vendor prefixes only where needed (see <a href="https://www.npmjs.com/package/gulp-autoprefixer/">gulp-autoprefixer</a>).</li> + <li>babel to transpile any new JavaScript syntax features to traditional syntax that works in older browsers (see <a href="https://www.npmjs.com/package/gulp-babel/">gulp-babel</a>).</li> +</ul> + +<p>See the links above for full instructions on the different gulp packages we are using.</p> + +<p>To use each plugin, you need to first install it via npm, then require any dependencies at the top of the <code>gulpfile.js</code> file, then add your test(s) to the bottom of it, and finally add the name of your task inside the <code>default</code> task.</p> + +<p>Before you go any further, update the default task to this:</p> + +<pre class="brush: js">gulp.task('default', [ ]);</pre> + +<p>Inside the array goes the names of all the tasks you want Gulp to run, once you run the <code>gulp</code> command on the command line.</p> + +<h4 id="html-tidy">html-tidy</h4> + +<ol> + <li>Install using the following line: + <pre class="brush: bash"><span class="text"><span>npm install --save-dev gulp-htmltidy</span></span> +</pre> + + <div class="note"> + <p><strong>Note</strong>: <code>--save-dev</code> adds the package as a dependency to your project. If you look in your project's <code>package.json</code> file, you'll see an entry for it as, it has been added to the <code>devDependencies</code> property.</p> + </div> + </li> + <li class="line">Add the following dependencies to <code>gulpfile.js</code>: + <pre class="brush: js">var htmltidy = require('gulp-htmltidy');</pre> + </li> + <li>Add the following test to the bottom of <code>gulpfile.js</code>: + <pre class="brush: js">gulp.task('html', function() { + return gulp.src('src/index.html') + .pipe(htmltidy()) + .pipe(gulp.dest('build')); +});</pre> + </li> + <li>Add <code>'html'</code> as an item inside the array in the <code>default</code> task.</li> +</ol> + +<p>Here we are grabbing our development <code>index.html</code> file — <code>gulp.src()</code> which allows us to grab a source file to do something with.</p> + +<p>We next use the <code>pipe()</code> function to pass that source to another command to do something else with. We can chain as many of these together as we want. We first run <code>htmltidy()</code> on the source, which goes through and fixes errors in our file. The second <code>pipe()</code> function writes the output HTML file to the <code>build</code> directory.</p> + +<p>In the input version of the file, you may have noticed that we put an empty {{htmlelement("p")}} element; htmltidy has removed this by the time the output file has been created.</p> + +<h4 id="Autoprefixer_and_css-lint">Autoprefixer and css-lint</h4> + +<ol> + <li>Install using the following lines: + <pre class="brush: bash">npm install --save-dev gulp-autoprefixer +npm install --save-dev gulp-csslint</pre> + </li> + <li>Add the following dependencies to <code>gulpfile.js</code>: + <pre class="brush: js">var autoprefixer = require('gulp-autoprefixer'); +var csslint = require('gulp-csslint');</pre> + </li> + <li>Add the following test to the bottom of <code>gulpfile.js</code>: + <pre class="brush: js">gulp.task('css', function() { + return gulp.src('src/style.css') + .pipe(csslint()) + .pipe(csslint.formatter('compact')) + .pipe(autoprefixer({ + browsers: ['last 5 versions'], + cascade: false + })) + .pipe(gulp.dest('build')); +});</pre> + </li> + <li>Add <code>'css'</code> as an item inside the array in the <code>default</code> task.</li> +</ol> + +<p>Here we grab our <code>style.css</code> file, run csslint on it (which outputs a list of any errors in your CSS to the terminal), then runs it through autoprefixer to add any prefixes needed to make nascent CSS features run in older browsers. At the end of the pipe chain, we output our modified prefixed CSS to the <code>build </code>directory. Note that this only works if csslint doesn't find any errors — try removing a curly brace from your CSS file and re-running gulp to see what output you get!</p> + +<h4 id="js-hint_and_babel">js-hint and babel</h4> + +<ol> + <li>Install using the following lines: + <pre class="editor editor-colors">npm install --save-dev gulp-babel babel-preset-es2015 +<span class="shell source"><span>npm install jshint gulp-jshint --save-dev</span></span> +</pre> + </li> + <li>Add the following dependencies to <code>gulpfile.js</code>: + <pre class="editor editor-colors">var babel = require('gulp-babel'); +<span class="js source"><span class="js storage type"><span>var</span></span><span> jshint </span><span class="assignment js keyword operator"><span>=</span></span><span> </span><span class="function-call js meta"><span class="function js support"><span>require</span></span><span class="js meta"><span class="begin definition js punctuation round"><span>(</span></span><span class="js quoted single string"><span class="begin definition js punctuation string"><span>'</span></span><span>gulp-jshint</span><span class="definition end js punctuation string"><span>'</span></span></span><span class="definition end js punctuation round"><span>)</span></span></span></span><span class="js punctuation statement terminator"><span>;</span></span></span> +</pre> + </li> + <li class="line">Add the following test to the bottom of <code>gulpfile.js</code>: + <pre class="brush: js">gulp.task('js', function() { + return gulp.src('src/main.js') + .pipe(jshint()) + .pipe(jshint.reporter('default')) + .pipe(babel({ + presets: ['es2015'] + })) + .pipe(gulp.dest('build')); +});</pre> + </li> + <li>Add <code>'js'</code> as an item inside the array in the <code>default</code> task.</li> +</ol> + +<p>Here we grab our <code>main.js</code> file, run <code>jshint</code> on it and output the results to the terminal using <code>jshint.reporter</code>; we then pass the file to babel, which converts it to old style syntax and outputs the result into the <code>build</code> directory. Our original code included a <a href="/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">fat arrow function</a>, which babel has modified into an old style function.</p> + +<h4 id="Further_ideas">Further ideas</h4> + +<p>Once all this is all set up, you can run the <code>gulp</code> command inside your project directory, and you should get an output like this:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14227/gulp-output.png" style="display: block; height: 478px; margin: 0px auto; width: 697px;"></p> + +<p>You can then try out the files output by your automated tasks by looking at them inside the <code>build</code> directory, and loading <code>build/index.html</code> in your web browser.</p> + +<p>If you get errors, check that you've added all the dependencies and the tests as shown above; also try commenting out the HTML/CSS/JavaScript code sections and then rerunning gulp to see if you can isolate what the problem is.</p> + +<p>Gulp comes with a <code>watch()</code> function that you can use to watch your files and run tests whenever you save a file. For example, try adding the following to the bottom of your <code>gulpfile.js</code>:</p> + +<pre class="brush: js">gulp.task('watch', function(){ + gulp.watch('src/*.html', ['html']); + gulp.watch('src/*.css', ['css']); + gulp.watch('src/*.js', ['js']); +});</pre> + +<p>Now try entering the <code>gulp watch</code> command into your terminal. Gulp will now watch your directory, and run the appropriate tasks whenever you save a change to an HTML, CSS, or JavaScript file.</p> + +<div class="note"> +<p><strong>Note</strong>: The <code>*</code> character is a wildcard character — here we're saying "run these tasks when any files of these types are saved. You could also use wildcards in your main tasks, for example <code>gulp.src('src/*.css')</code> would grab all your CSS files and then run piped tasks on them.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: One problem with our watch command above is that our CSSLint/Autoprefixer combination throws full-blown errors when a CSS error is encountered, which stops the watch working. You'll have to restart the watch once a CSS error is encountered, or find another way to do this.</p> +</div> + +<p>There's a lot more you can do with Gulp. The <a href="http://gulpjs.com/plugins/">Gulp plugin directory</a> has literally thousands of plugins to search through.</p> + +<h3 id="Other_task_runners">Other task runners</h3> + +<p>There are many other task runners available. We certainly aren't trying to say that Gulp is the best solution out there, but it works for us and it is fairly accessible to beginners. You could also try using other solutions:</p> + +<ul> + <li>Grunt works in a very similar way to Gulp, except that it relies on tasks specified in a config file, rather than using written JavaScript. See <a href="http://gruntjs.com/getting-started">Getting started with Grunt for more details.</a></li> + <li>You can also run tasks directly using npm scripts located inside your <code>package.json</code> file, without needing to install any kind of extra task runner system. This works on the premise that things like Gulp plugins are basically wrappers around command line tools. So, if you can work out how to run the tools using the command line, you can then run them using npm scripts. It is a bit trickier to work with, but can be rewarding for those who are strong with their command line skills.<a href="https://css-tricks.com/why-npm-scripts/"> Why npm scripts?</a> provides a good introduction with a good deal of further information.</li> +</ul> + +<h2 id="使用商业测试服务加快浏览器测试">使用商业测试服务加快浏览器测试</h2> + +<p>Now let's look at commercial 3rd party browser testing services and what they can do for us.</p> + +<p>The basic premise with such applications is that the company that runs each one has a huge server farm that can run many different tests. When you use this service, you provide a URL of the page you want to test along with information, such as what browsers you want it tested in. The app then configures a new VM with the OS and browser you specified, and returns the test results in the form of screenshots, videos, logfiles, text, etc.</p> + +<p>You can then step up a gear, using an API to access functionality programmatically, which means that such apps can be combined with task runners, such as your own local Selenium environments and others, to create automated tests.</p> + +<div class="note"> +<p><strong>Note</strong>: There are other commercial browser testing systems available but in this article, we'll focus on Sauce Labs and BrowserStack. We're not saying that these are necessarily the best tools available, but they are good ones that are simple for beginners to get up and running with.</p> +</div> + +<h3 id="Sauce_Labs">Sauce Labs</h3> + +<h4 id="Sauce_Labs入门">Sauce Labs入门</h4> + +<p>Let's get started with a Sauce Labs Trial.</p> + +<ol> + <li>Create a <a href="https://saucelabs.com/signup/trial">Sauce Labs trial account</a>.</li> + <li>Sign in. This should happen automatically after you verify your e-mail address.</li> +</ol> + +<h4 id="The_basics_Manual_tests">The basics: Manual tests</h4> + +<p>The <a href="https://saucelabs.com/beta/dashboard/manual">Sauce Labs dashboard</a> has a lot of options available on it. For now, make sure you are on the <em>Manual Tests</em> tab.</p> + +<ol> + <li>Click <em>Start a new manual session</em>.</li> + <li>In the next screen, type in the URL of a page you want to test (use <a href="http://mdn.github.io/learning-area/javascript/building-blocks/events/show-video-box-fixed.html">http://mdn.github.io/learning-area/javascript/building-blocks/events/show-video-box-fixed.html</a>, for example), then choose a browser/OS combination you want to test by using the different buttons and lists. There is a lot of choice, as you'll see! <img alt="" src="https://mdn.mozillademos.org/files/14229/sauce-manual-session.png" style="border-style: solid; border-width: 1px; display: block; height: 636px; margin: 0px auto; width: 600px;"></li> + <li>When you click Start session, a loading screen will then appear, which spins up a virtual machine running the combination you chose.</li> + <li>When loading has finished, you can then start to remotely test the web site running in the chosen browser. <img alt="" src="https://mdn.mozillademos.org/files/14231/sauce-test-running.png" style="display: block; height: 390px; margin: 0px auto; width: 800px;"></li> + <li>From here you can see the layout as it would look in the browser you are testing, move the mouse around and try clicking buttons, etc. The top menu allows you to: + <ul> + <li>Stop the session</li> + <li>Give someone else a URL so they can observe the test remotely.</li> + <li>Copy text/notes to a remote clipboard.</li> + <li>Take a screenshot.</li> + <li>Test in full screen mode.</li> + </ul> + </li> +</ol> + +<p>Once you stop the session, you'll return to the Manual Tests tab, where you'll see an entry for each of the previous manual sessions you started. Clicking on one of these entries shows more data for the session. In here you can download any screenshots you took , watch a video of the session, and view data logs for the session for example.</p> + +<div class="note"> +<p><strong>Note</strong>: This is already very useful, and way more convenient than having to set up all these emulators and virtual machines by yourself.</p> +</div> + +<h4 id="Advanced_The_Sauce_Labs_API">Advanced: The Sauce Labs API</h4> + +<p>Sauce Labs has a <a href="https://wiki.saucelabs.com/display/DOCS/The+Sauce+Labs+REST+API">restful API</a> that allows you to programmatically retrieve details of your account and existing tests, and annotate tests with further details, such as their pass/fail state which isn't recordable by manual testing alone. For example, you might want to run one of your own Selenium tests remotely using Sauce Labs, to test a certain browser/OS combination, and then pass the test results back to Sauce Labs.</p> + +<p>It has several clients available to allow you to make calls to the API using your favourite environment, be it PHP, Java, Node.js, etc.</p> + +<p>Let's have a brief look at how we'd access the API using Node.js and <a href="https://github.com/danjenkins/node-saucelabs">node-saucelabs</a>.</p> + +<ol> + <li>First, set up a new npm project to test this out, as detailed in {{anch("Setting up Node and npm")}}. Use a different directory name than before, like <code>sauce-test</code> for example.</li> + <li>Install the Node Sauce Labs wrapper using the following command: + <pre>npm install saucelabs</pre> + </li> + <li>Create a new file inside your project root called <code>call_sauce.js</code>. give it the following contents: + <pre class="brush: js">var SauceLabs = require('saucelabs'); + +var myAccount = new SauceLabs({ + username: "your-sauce-username", + password: "your-sauce-api-key" +}); + +myAccount.getAccountDetails(function (err, res) { + console.log(res); + myAccount.getServiceStatus(function (err, res) { + // Status of the Sauce Labs services + console.log(res); + myAccount.getJobs(function (err, jobs) { + // Get a list of all your jobs + for (var k in jobs) { + if ( jobs.hasOwnProperty( k )) { + myAccount.showJob(jobs[k].id, function (err, res) { + var str = res.id + ": Status: " + res.status; + if (res.error) { + str += "\033[31m Error: " + res.error + " \033[0m"; + } + console.log(str); + }); + } + } + }); + }); +});</pre> + </li> + <li>You'll need to fill in your Sauce Labs username and API key in the indicated places. These can be retrieved from your <a href="https://saucelabs.com/beta/user-settings">User Settings</a> page. Fill these in now.</li> + <li>Make sure everything is saved, and run your file like so: + <pre class="brush: bash">node call_sauce</pre> + </li> +</ol> + +<h4 id="Advanced_Automated_tests">Advanced: Automated tests</h4> + +<p>We'll cover actually running automated Sauce Lab tests in the next article.</p> + +<h3 id="BrowserStack">BrowserStack</h3> + +<h4 id="BrowserStack入门">BrowserStack入门</h4> + +<p>Let's get started with a BrowserStack Trial.</p> + +<ol> + <li>Create a <a href="https://www.browserstack.com/users/sign_up">BrowserStack trial account</a>.</li> + <li>Sign in. This should happen automatically after you verify your e-mail address.</li> + <li>When you first sign in, you should be on the Live testing page; if not, click the <em>Live</em> link in the top nav menu.</li> + <li>If you are on Firefox or Chrome, you'll be prompted to Install a browser extension in a dialog titled "Enable Local Testing" — click the <em>Install</em> button to proceed. If you are on other browsers you'll still be able to use some of the features (generally via Flash), but you won't get the full experience.</li> +</ol> + +<h4 id="基础:手动测试">基础:手动测试</h4> + +<p>The BrowserStack Live dashboard allows you to choose what device and browser you want to test on — Platforms in the left column, devices on the right. When you mouse over or click on each device, you get a choice of browsers available on that device.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15377/browserstack-test-choices-sized.png" style="border-style: solid; border-width: 1px; display: block; height: 339px; margin: 0px auto; width: 700px;"></p> + +<p>Clicking on one of those browser icons will load up your choice of platform/device/browser — choose one now, and give it a try.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15379/browserstack-test-device-sized.png" style="border-style: solid; border-width: 1px; display: block; height: 841px; margin: 0px auto; width: 450px;"></p> + +<div class="note"> +<p><strong>Note</strong>: The blue device icon next to some of the mobile device choices signals that you will be testing on a real device; choices without that icon will be run on an emulator.</p> +</div> + +<p>You'll find that you can enter URLs into the address bar, and use the other controls like you'd expect on a real device. You can even do things like copy and paste from the device to your clipboard, scroll up and down by dragging with the mouse, or use appropriate gestures (e.g. pinch/zoom, two fingers to scroll) on the touchpads of supporting devices (e.g. Macbook). Note that not all features are available on all devices.</p> + +<p>You'll also see a menu that allows you to control the session.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15381/browserstack-test-menu-sized.png" style="border-style: solid; border-width: 1px; display: block; height: 636px; margin: 0px auto; width: 217px;"></p> + +<p>The features here are as follows:</p> + +<ul> + <li><em>Switch</em> — Change to another platform/device/browser combination.</li> + <li>Orientation (looks like a Reload icon) — Switch orientation between portrait and landscape.</li> + <li>Fit to screen (looks like a full screen icon) — Fill the testing areas as much as possible with the device.</li> + <li>Capture a bug (looks like a camera) — Takes a screenshot, then allows you to annotate and save it.</li> + <li>Issue tracker (looks like a deck of cards) — View previously captured bugs/screenshots.</li> + <li>Settings (cog icon) — Allows you to alter general settings for the session.</li> + <li>Help (question mark) — Accesses help/support functions.</li> + <li><em>Devtools</em> — Allows you to use your browser's devtools to directly debug or manipulate the page being shown in the test browser. This currently only works when testing the Safari browser on iOS devices.</li> + <li><em>Device info</em> — Displays information about the testing device.</li> + <li><em>Features</em> — Shows you what features the current configuration supports, e.g. copy to clipboard, gesture support, etc.</li> + <li><em>Stop</em> — Ends the session.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: This is already very useful, and way more convenient than having to set up all these emulators and virtual machines by yourself.</p> +</div> + +<h4 id="其他基本功能">其他基本功能</h4> + +<p>If you go back to the main BrowserStack page, you'll find a couple of other useful basic features under the <em>More</em> menu option:</p> + +<ul> + <li><em>Responsive</em>: Enter a URL and press <em>Generate</em>, and BrowserStack will load that URL on multiple devices with different viewport sizes. Within each device you can further adjust settings like monitor size, to get a good idea of how your site's layout works across different form factors.</li> + <li><em>Screenshots</em>: Enter a URL, choose the browsers/devices/platforms you are interested in, then press <em>Generate screenshots</em> — Browserstack will take screenshots of your site in all those different browsers then make them available to you to view and download.</li> +</ul> + +<h4 id="高级_BrowserStack_API">高级: BrowserStack API</h4> + +<p>BrowserStack also has a <a href="https://www.browserstack.com/automate/rest-api">restful API</a> that allows you to programmatically retrieve details of your account plan, sessions, builds, etc.</p> + +<p>It has several clients available to allow you to make calls to the API using your favourite environment, be it PHP, Java, Node.js, etc.</p> + +<p>Let's have a brief look at how we'd access the API using Node.js.</p> + +<ol> + <li>First, set up a new npm project to test this out, as detailed in {{anch("Setting up Node and npm")}}. Use a different directory name than before, like <code>bstack-test</code> for example.</li> + <li>Create a new file inside your project root called <code>call_bstack.js</code>. give it the following contents: + <pre class="brush: js">var request = require("request"); + +var bsUser = "BROWSERSTACK_USERNAME"; +var bsKey = "BROWSERSTACK_ACCESS_KEY"; +var baseUrl = "https://" + bsUser + ":" + bsKey + "@www.browserstack.com/automate/"; + +function getPlanDetails(){ + request({uri: baseUrl + "plan.json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + { + automate_plan: <string>, + parallel_sessions_running: <int>, + team_parallel_sessions_max_allowed: <int>, + parallel_sessions_max_allowed: <int>, + queued_sessions: <int>, + queued_sessions_max_allowed: <int> + } + */ +} + +getPlanDetails(); +</pre> + </li> + <li>You'll need to fill in your BrowserStack username and API key in the indicated places. These can be retrieved from your <a href="https://www.browserstack.com/automate">BrowserStack automation dashboard</a>. Fill these in now.</li> + <li>Make sure everything is saved, and run your file like so: + <pre class="brush: bash">node call_bstack</pre> + </li> +</ol> + +<p>Below we've also provided some other ready-made functions you might find useful when working with the BrowserStack restful API.</p> + +<pre class="brush: js">function getBuilds(){ + request({uri: baseUrl + "builds.json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + [ + { + automation_build: { + name: <string>, + duration: <int>, + status: <string>, + hashed_id: <string> + } + }, + { + automation_build: { + name: <string>, + duration: <int>, + status: <string>, + hashed_id: <string> + } + }, + ... + ] + */ +}; + +function getSessionsInBuild(build){ + var buildId = build.automation_build.hashed_id; + request({uri: baseUrl + "builds/" + buildId + "/sessions.json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + [ + { + automation_session: { + name: <string>, + duration: <int>, + os: <string>, + os_version: <string>, + browser_version: <string>, + browser: <string>, + device: <string>, + status: <string>, + hashed_id: <string>, + reason: <string>, + build_name: <string>, + project_name: <string>, + logs: <string>, + browser_url: <string>, + public_url: <string>, + video_url: <string>, + browser_console_logs_url: <string>, + har_logs_url: <string> + } + }, + { + automation_session: { + name: <string>, + duration: <int>, + os: <string>, + os_version: <string>, + browser_version: <string>, + browser: <string>, + device: <string>, + status: <string>, + hashed_id: <string>, + reason: <string>, + build_name: <string>, + project_name: <string>, + logs: <string>, + browser_url: <string>, + public_url: <string>, + video_url: <string>, + browser_console_logs_url: <string>, + har_logs_url: <string> + } + }, + ... + ] + */ +} + +function getSessionDetails(session){ + var sessionId = session.automation_session.hashed_id; + request({uri: baseUrl + "sessions/" + sessionId + ".json"}, function(err, res, body){ + console.log(JSON.parse(body)); + }); + /* Response: + { + automation_session: { + name: <string>, + duration: <int>, + os: <string>, + os_version: <string>, + browser_version: <string>, + browser: <string>, + device: <string>, + status: <string>, + hashed_id: <string>, + reason: <string>, + build_name: <string>, + project_name: <string>, + logs: <string>, + browser_url: <string>, + public_url: <string>, + video_url: <string>, + browser_console_logs_url: <string>, + har_logs_url: <string> + } + } + */ +}</pre> + +<h4 id="高级_自动化测试">高级: 自动化测试</h4> + +<p>We'll cover actually running automated BrowserStack tests in the next article.</p> + +<h2 id="总结">总结</h2> + +<p>This was quite a ride, but I'm sure you can start to see the benefit in having automation tools do a lot of the heavy lifting for you in terms of testing.</p> + +<p>In the next article, we'll look at setting up our own local automation system using Selenium, and how to combine that with services such as Sauce Labs and BrowserStack</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/feature_detection/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/feature_detection/index.html new file mode 100644 index 0000000000..34f3e66ba4 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/feature_detection/index.html @@ -0,0 +1,344 @@ +--- +title: Implementing feature detection +slug: Learn/Tools_and_testing/Cross_browser_testing/Feature_detection +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Feature_detection +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Accessibility","Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">特性检测包括计算浏览器是否支持一个特定块中的代码,基于是否可以运行来运行不同的代码,以使所有的浏览器都可以正常工作,而不是在某些浏览器中崩溃或出错。本文列举了如何写出一个你自己的简单的特性检测,如何使用库来加速实现,以及对于特性间的的原生特性,如 <code>@supports</code> 。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages; an idea of the high-level <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">principles of cross-browser testing</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To understand what the concept of feature detection is, and be able to implement suitable solutions in CSS and JavaScript.</td> + </tr> + </tbody> +</table> + +<h2 id="特性检测的概念">特性检测的概念</h2> + +<p>功能检测的思想是,您可以运行一个测试来确定当前浏览器是否支持某个特性,然后有条件地运行代码,以便在支持该功能的浏览器和不支持该功能的浏览器中提供可接受的体验,如果不这样做,在不支持您在代码中使用的功能的浏览器中将无法正确地显示您的网站,从而产生糟糕的用户体验。</p> + +<p>Let's recap and look at the example we touched on in our <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#Feature_detection">Handling common JavaScript problems</a> — the <a href="/en-US/docs/Web/API/Geolocation/Using_geolocation">Geolocation API</a> (which exposes available location data for the device the web browser is running on) has the main entry point for its use, a <code>geolocation</code> property available on the global <a href="/en-US/docs/Web/API/Navigator">Navigator</a> object. Therefore, you can detect whether the browser supports geolocation or not by using something like the following:</p> + +<pre class="language-js notranslate">if ("geolocation" in navigator) { + navigator.geolocation.getCurrentPosition(function(position) { + // show the location on a map, perhaps using the Google Maps API + }); +} else { + // Give the user a choice of static maps instead perhaps +}</pre> + +<p>It is probably better to use an established feature detection library however, rather than writing your own all the time. Modernizr is the industry standard for feature detection tests, and we'll look at that later on.</p> + +<p>Before we move on, we'd like to say one thing upfront — don't confuse feature detection with <strong>browser sniffing</strong> (detecting what specific browser is accessing the site) — this is a terrible practice that should be discouraged at all costs. See <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#Using_bad_browser_sniffing_code">Using bad browser sniffing code</a> for more details.</p> + +<h2 id="Writing_your_own_feature_detection_tests">Writing your own feature detection tests</h2> + +<p>In this section, we'll look at implementing your own feature detection tests, in both CSS and JavaScript.</p> + +<h3 id="CSS">CSS</h3> + +<p>You can write tests for CSS features by testing for the existence of <em><a href="/en-US/docs/Web/API/HTMLElement/style">element.style.property</a></em> (e.g. <code>paragraph.style.transform</code>) in JavaScript.</p> + +<p>A classic example might be to test for <a href="/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a> support in a browser; for browsers that support the newest Flexbox spec, we could use a flexible and robust flex layout. For browsers that don't, we could use a floated layout that works OK, although it is slightly more brittle and hacky, and not as cool-looking.</p> + +<p>Let's implement something that demonstrates this, although we'll keep it simple for now.</p> + +<ol> + <li>Start by making local copies of our <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/css-feature-detect.html">css-feature-detect.html</a></code>, <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/flex-layout.css">flex-layout.css</a></code>, <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/float-layout.css">float-layout.css</a></code>, and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/basic-styling.css">basic-styling.css</a></code> files. Save them in a new directory.</li> + <li>We will add the HTML5 Shiv to our example too so that the HTML5 semantic elements will style properly in older versions of IE. Download the latest version (see <a href="https://github.com/aFarkas/html5shiv#manual-installation">Manual installation</a>), unzip the ZIP file, copy the <code>html5shiv-printshiv.min.js</code> and <code>html5shiv.min.js</code> files into your example directory, and link to one of the files by putting the following under your {{htmlelement("title")}} element: + <pre class="notranslate"><script src="html5shiv.min.js"></script></pre> + </li> + <li>Have a look at your example CSS files — you'll see that <code>basic-styling.css</code> handles all the styling that we want to give to every browser, whereas the other two CSS files contain the CSS we want to selectively apply to browser depending on their support levels. You can look at the different effects these two files have by manually changing the CSS file referred to by the second {{htmlelement("link")}} element, but let's instead implement some JavaScript to automatically swap them as needed.</li> + <li>First, remove the contents of the second <code><link></code> element's <code>href</code> attribute. We will fill this in dynamically later on.</li> + <li>Next, add a <code><script></script></code> element at the bottom of your body (just before the closing <code></body></code> tag).</li> + <li>Give it the following contents: + <pre class="brush: js notranslate">const conditional = document.querySelector('.conditional'); +const testElem = document.createElement('div'); +if (testElem.style.flex !== undefined && testElem.style.flexFlow !== undefined) { + conditional.setAttribute('href', 'flex-layout.css'); +} else { + conditional.setAttribute('href', 'float-layout.css'); +}</pre> + </li> +</ol> + +<p>Here we are grabbing a reference to the second <code><link></code> element, and creating a <code><div></code> element as part of our test. In our conditional statement, we test that the {{cssxref("flex")}} and {{cssxref("flex-flow")}} properties exist in the browser. Note how the JavaScript representations of those properties that are stored inside the {{domxref("HTMLElement.style")}} object use lower camel case, not hyphens, to separate the words.</p> + +<div class="note"> +<p><strong>Note</strong>: If you have trouble getting this to work, you can compare it to our <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/css-feature-detect-finished.html">css-feature-detect-finished.html</a> code (see also the <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/feature-detection/css-feature-detect-finished.html">live version</a>).</p> +</div> + +<p>When you save everything and try out your example, you should see the flexbox layout applied to the page if the browser supports modern flexbox, and the float layout if not.</p> + +<div class="note"> +<p><strong>Note</strong>: Often such an approach is overkill for a minor feature detection problem — you can often get away with using multiple vendor prefixes and fallback properties, as described in <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#CSS_fallback_behaviour">CSS fallback behavior</a> and <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Handling_CSS_prefixes">Handling CSS prefixes</a>.</p> +</div> + +<h4 id="supports">@supports</h4> + +<p>In recent times, CSS has had its own native feature detection mechanism introduced — the {{cssxref("@supports")}} at-rule. This works in a similar manner to <a href="/en-US/docs/Web/CSS/Media_Queries">media queries</a> (see also <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Responsive_design_problems">Responsive design problems</a>) — except that instead of selectively applying CSS depending on a media feature like a resolution, screen width or aspect ratio, it selectively applies CSS depending on whether a CSS feature is supported.</p> + +<p>For example, we could rewrite our previous example to use <code>@supports</code> — see <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-feature-detect.html">supports-feature-detect.html</a></code> and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-styling.css">supports-styling.css</a></code>. If you look at the latter, you'll see a couple of <code>@supports</code> blocks, for example:</p> + +<pre class="brush: css notranslate">@supports (flex-flow: row) and (flex: 1) { + + main { + display: flex; + } + + main div { + padding-right: 4%; + flex: 1; + } + + main div:last-child { + padding-right: 0; + } + +}</pre> + +<p>This at-rule block applies the CSS rule within only if the current browser supports both the <code>flex-flow: row</code> and <code>flex: 1</code> declarations. For each condition to work, you need to include a complete declaration (not just a property name) and NOT include the semi-colon on the end.</p> + +<p><code>@supports</code> also has <code>OR</code> and <code>NOT</code> logic available — the other block applies the float layout if the flexbox properties are not available:</p> + +<pre class="brush: css notranslate">@supports not (flex-flow: row) and (flex: 1) { + + /* rules in here */ + +}</pre> + +<p>This may look a lot more convenient than the previous example — we can do all of our feature detection in CSS, no JavaScript required, and we can handle all the logic in a single CSS file, cutting down on HTTP requests. the problem here is browser support — <code>@supports</code> is not supported at all in IE, and only supported in very recent versions of Safari/iOS WebKit (9+/9.2+), whereas the JavaScript version should work in much older browsers (probably back to IE8 or 9, although older versions of IE will have additional problems, such as not supporting {{domxref("Document.querySelector")}}, and having a messed up box model).</p> + +<h3 id="JavaScript">JavaScript</h3> + +<p>We already saw an example of a JavaScript feature detection test earlier on. Generally, such tests are done via one of the following common patterns:</p> + +<table class="standard-table"> + <caption>Summary of JavaScript feature detection techniques</caption> + <thead> + <tr> + <th scope="col">Feature detection type</th> + <th scope="col">Explanation</th> + <th scope="col">Example</th> + </tr> + </thead> + <tbody> + <tr> + <td><em>If member in object</em></td> + <td>Check whether a certain method or property (typically an entry point into using the API or other feature you are detecting for) exists in its parent Object.</td> + <td> + <p><code>if("geolocation" in navigator) { ... }</code></p> + </td> + </tr> + <tr> + <td><em>Property on element</em></td> + <td>Create an element in memory using {{domxref("Document.createElement()")}} and then check if a property exists on it. The example shown is a way of detecting <a href="/en-US/docs/Web/API/Canvas_API">HTML5 Canvas</a> support.</td> + <td><code>function supports_canvas() {<br> + return !!document.createElement('canvas').getContext;<br> + }<br> + <br> + if(supports_canvas()) { ... }</code></td> + </tr> + <tr> + <td><em>Method on element return value</em></td> + <td>Create an element in memory using {{domxref("Document.createElement()")}} and then check if a method exists on it. If it does, check what value it returns.</td> + <td>See <a href="http://diveinto.html5doctor.com/detect.html#video-formats">Dive Into HTML5 Video Formats detection</a> test.</td> + </tr> + <tr> + <td><em>Property on element retains value</em></td> + <td>Create an element in memory using {{domxref("Document.createElement()")}}, set a property to a certain value, then check to see if the value is retained.</td> + <td>See <a href="http://diveinto.html5doctor.com/detect.html#input-types">Dive into HTML5 <code><input></code> types detection</a> test.</td> + </tr> + </tbody> +</table> + +<div class="note"> +<p><strong>Note</strong>: The double <code>NOT</code> in the above example (<code>!!</code>) is a way to force a return value to become a "proper" boolean value, rather than a {{glossary("Truthy")}}/{{glossary("Falsy")}} value that may skew the results.</p> +</div> + +<p>The <a href="http://diveinto.html5doctor.com/detect.html">Dive into HTML5 Detecting HTML5 Features</a> page has a lot more useful feature detection tests besides the ones listed above, and you can generally find a feature detection test for most things by searching for "detect support for YOUR-FEATURE-HERE" in your favorite search engine. Bear in mind though that some features, however, are known to be undetectable — see Modernizr's list of <a href="https://github.com/Modernizr/Modernizr/wiki/Undetectables">Undetectables</a>.</p> + +<h4 id="matchMedia">matchMedia</h4> + +<p>We also wanted to mention the {{domxref("Window.matchMedia")}} JavaScript feature at this point too. This is a property that allows you to run media query tests inside JavaScript. It looks like this:</p> + +<pre class="brush: js notranslate">if (window.matchMedia("(max-width: 480px)").matches) { + // run JavaScript in here. +}</pre> + +<p>As an example, our <a href="https://github.com/chrisdavidmills/snapshot">Snapshot</a> demo makes use of it to selectively apply the Brick JavaScript library and use it to handle the UI layout, but only for the small screen layout (480px wide or less). We first use the <code>media</code> attribute to only apply the Brick CSS to the page if the page width is 480px or less:</p> + +<pre class="brush: css notranslate"><link href="dist/brick.css" type="text/css" rel="stylesheet" media="all and (max-width: 480px)"></pre> + +<p>We then use <code>matchMedia()</code> in the JavaScript several times, to only run Brick navigation functions if we are on the small screen layout (in wider screen layouts, everything can be seen at once, so we don't need to navigate between different views).</p> + +<pre class="brush: js notranslate">if (window.matchMedia("(max-width: 480px)").matches) { + deck.shuffleTo(1); +}</pre> + +<h2 id="Using_Modernizr_to_implement_feature_detection">Using Modernizr to implement feature detection</h2> + +<p>It is possible to implement your own feature detection tests using techniques like the ones detailed above. You might as well use a dedicated feature detection library however, as it makes things much easier. The mother of all feature detection libraries is <a href="https://modernizr.com/">Modernizr</a>, and it can detect just about everything you'll ever need. Let's look at how to use it now.</p> + +<p>When you are experimenting with Modernizr you might as well use the development build, which includes every possible feature detection test. Download this now by:</p> + +<ol> + <li>Clicking on the <a href="https://modernizr.com/download?do_not_use_in_production">Development build</a> link.</li> + <li>Clicking the big pink <em>Build</em> button on the page that comes up.</li> + <li>Clicking the top <em>Download</em> link in the dialog box that appears.</li> +</ol> + +<p>Save it somewhere sensible, like the directory you've been creating your other examples for in this article.</p> + +<p>When you are using Modernizr in production, you can go to the <a href="https://modernizr.com/download">Download page</a> you've already visited and click the plus buttons for only the features you need feature detects for. Then when you click the <em>Build</em> button, you'll download a custom build containing only those feature detects, making for a much smaller file size.</p> + +<h3 id="CSS_2">CSS</h3> + +<p>Let's have a look at how Modernizr works in terms of selectively applying CSS.</p> + +<ol> + <li>First, make a copy of <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-feature-detect.html">supports-feature-detect.html</a></code> and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/supports-styling.css">supports-styling.css</a></code>. Save them as <code>modernizr-css.html</code> and <code>modernizr-css.css</code>.</li> + <li>Update your {{htmlelement("link")}} element in your HTML so it points to the correct CSS file (you should also update your {{htmlelement("title")}} element to something more suitable!): + <pre class="brush: html notranslate"><link href="modernizr-css.css" rel="stylesheet"></pre> + </li> + <li>Above this <code><link></code> element, add a {{htmlelement("script")}} element to apply the Modernizr library to the page, as shown below. This needs to be applied to the page before any CSS (or JavaScript) that might make use of it. + <pre class="brush: html notranslate"><script src="modernizr-custom.js"></script></pre> + </li> + <li>Now edit your opening <code><html></code> tag, so that it looks like this: + <pre class="brush: html notranslate"><html class="no-js"></pre> + </li> +</ol> + +<p>At this point, try loading your page, and you'll get an idea of how Modernizr works for CSS features. If you look at the DOM inspector of your browser's developer tools, you'll see that Modernizr has updated your <code><html></code> <code>class</code> value like so:</p> + +<pre class="notranslate"><html class="js no-htmlimports sizes flash transferables applicationcache blobconstructor +blob-constructor cookies cors ...AND LOADS MORE VALUES!></pre> + +<p>It now contains a large number of classes that indicate the support status of different technology features. As an example, if the browser didn't support flexbox at all, <code><html></code> would be given a class name of <code>no-flexbox</code>. If it did support modern flexbox, it would get a class name of <code>flexbox</code>. If you search through the class list, you'll also see others relating to flexbox, like:</p> + +<ul> + <li><code>flexboxlegacy</code> for the old flexbox spec (2009).</li> + <li><code>flexboxtweener</code> for 2011 in between syntax supported by IE10.</li> + <li><code>flexwrap</code> for the {{cssxref("flex-wrap")}} property, which isn't present in some implementations.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: You can find a list of what all the class names mean — see <a href="https://modernizr.com/docs#features">Features detected by Modernizr</a>.</p> +</div> + +<p>Moving on, let's update our CSS to use Modernizr rather than <code>@supports</code>. Go into <code>modernizr-css.css</code>, and replace the two <code>@supports</code> blocks with the following:</p> + +<pre class="brush: css notranslate">/* Properties for browsers with modern flexbox */ + +.flexbox main { + display: flex; +} + +.flexbox main div { + padding-right: 4%; + flex: 1; +} + +.flexbox main div:last-child { + padding-right: 0; +} + +/* Fallbacks for browsers that don't support modern flexbox */ + +.no-flexbox main div { + width: 22%; + float: left; + padding-right: 4%; +} + +.no-flexbox main div:last-child { + padding-right: 0; +} + +.no-flexbox footer { + clear: left; +}</pre> + +<p>So how does this work? Because all those class names have been put on the <code><html></code> element, you can target browsers that do or don't support a feature using specific descendant selectors. So here we're applying the top set of rules only to browsers that do support flexbox, and the bottom set of rules only to browsers that don't (<code>no-flexbox</code>).</p> + +<div class="note"> +<p><strong>Note</strong>: Bear in mind that all of Modernizr's HTML and JavaScript feature tests are also reported in these class names, so you can quite happily apply CSS selectively based on whether the browser supports HTML or JavaScript features, if needed.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: If you have trouble getting this to work, check your code against our <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-css.html">modernizr-css.html</a></code> and <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-css.css">modernizr-css.css</a></code> files (see this running live also).</p> +</div> + +<h3 id="JavaScript_2">JavaScript</h3> + +<p>Modernizr is also equally well-prepared for implementing JavaScript feature detects too. It does this by making the global <code>Modernizr</code> object available to the page it is applied to, which contains results of the feature detects as <code>true</code>/<code>false</code> properties.</p> + +<p>For example, load up our <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-css.html">modernizr-css.html</a></code> example in your browser, then try going to your JavaScript console and typing in <code>Modernizr.</code> followed by some of those class names (they are the same here too). For example:</p> + +<pre class="notranslate">Modernizr.flexbox +Modernizr.websqldatabase +Modernizr.xhr2 +Modernizr.fetch</pre> + +<p>The console will return <code>true</code>/<code>false</code> values to indicate whether your browser supports those features or not.</p> + +<p>Let's look at an example to show how you'd use those properties.</p> + +<ol> + <li>First of all, make a local copy of the <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/feature-detection/modernizr-js.html">modernizr-js.html</a></code> example file.</li> + <li>Attach the Modernizr library to the HTML using a <code><script></code> element, as we have done in previous demos. Put it above the existing <code><script></code> element, which is attaching the Google Maps API to the page.</li> + <li>Next, fill in the <code>YOUR-API-KEY</code> placeholder text in the second <code><script></code> element (as it is now) with a valid Google Maps API key. To get a key, sign in to a Google account, go to the <a href="https://developers.google.com/maps/documentation/javascript/get-api-key">Get a Key/Authentication</a> page, then click the blue <em>Get a Key</em> button and follow the instructions.</li> + <li>Finally, add another <code><script></code> element at the bottom of the HTML body (just before the <code></body></code> tag), and put the following script inside the tags: + <pre class="brush: js notranslate">if (Modernizr.geolocation) { + + navigator.geolocation.getCurrentPosition(function(position) { + + let latlng = new google.maps.LatLng(position.coords.latitude,position.coords.longitude); + let myOptions = { + zoom: 8, + center: latlng, + mapTypeId: google.maps.MapTypeId.TERRAIN, + disableDefaultUI: true + } + let map = new google.maps.Map(document.getElementById("map_canvas"), myOptions); + }); + +} else { + const para = document.createElement('p'); + para.textContent = 'Argh, no geolocation!'; + document.body.appendChild(para); +}</pre> + </li> +</ol> + +<p>Try your example out! Here we use the <code>Modernizr.geolocation</code> test to check whether geolocation is supported by the current browser. If it is, we run some code that gets your device's current location, and plots it on a Google Map.</p> + +<h2 id="Summary">Summary</h2> + +<p>This article covered feature detection in a reasonable amount of detail, going through the main concepts and showing you how to both implement your own feature detection tests and use the Modernizr library to implement tests more easily.</p> + +<p>Next up, we'll start looking at automated testing.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Accessibility","Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">Introduction to cross browser testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">Strategies for carrying out testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">Handling common HTML and CSS problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">Handling common accessibility problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Implementing feature detection</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">Introduction to automated testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Setting up your own test automation environment</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/html_and_css/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/html_and_css/index.html new file mode 100644 index 0000000000..0e2cd596de --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/html_and_css/index.html @@ -0,0 +1,496 @@ +--- +title: 处理常见的 HTML 和 CSS 问题 +slug: Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS +tags: + - CSS + - HTML + - 兼容 + - 学习 + - 文章 + - 测试 +translation_of: Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS +--- +<p>{{LearnSidebar}}</p> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies","Learn/Tools_and_testing/Cross_browser_testing/JavaScript", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p>通过之前的准备,我们现在将专门研究在 HTML 和 CSS 代码中遇到的常见跨浏览器问题,以及哪些工具可以用来防止问题的发生,或者解决出现的问题。这包括静态分析代码,处理CSS前缀,使用浏览器开发工具来跟踪问题,使用polyfills添加到浏览器的支持,解决响应的设计问题,等等。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td> + <p>熟悉核心的 <a href="/zh-CN/docs/Learn/HTML">HTML</a>,<a href="/zh-CN/docs/Learn/CSS">CSS</a> 和 <a href="/zh-CN/docs/Learn/JavaScript">JavaScript</a> 语言;了解<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试</a>中涉及的高级概念。</p> + </td> + </tr> + <tr> + <th scope="row">本文目标:</th> + <td>为了能够诊断常见的HTML和CSS跨浏览器问题,并使用适当的工具和技术来解决它们</td> + </tr> + </tbody> +</table> + +<h2 id="HTML_和_CSS_的问题">HTML 和 CSS 的问题</h2> + +<p>实际上,HTML 和 CSS 的问题在于,它们相当简单,简单到开发人员通常不会认真对待它们,因此难以确保代码简洁、高效,或是能在语义上描述页面各部分的功能。在最坏的情况下,一些网站甚至选择使用 JavaScript 来生成整个网页的内容和样式,这使得页面的可读性较差,并且性能低下(对性能而言,生成大量 DOM 元素的操作是较为昂贵的)。</p> + +<div class="standardNoteBlock"> +<p><strong>译者注:</strong>关于使用 JavaScript 生成网页的选择,抛开一切非决定性的因素,最终的关键还是我们开发者。<br> + 非决定性因素,比如页面本身,举个例子,如果页面是由大量的重复或有规律片段组成,使用适当、理想的 JavaScript 来生成这样的页面会是不错的选择。</p> + +<p>但是,如果我们的决定性因素,开发者,不了解或不重视有关的问题,甚至盲目地崇拜一些基于 JavaScript 的前端页面生成方案,可能一切都是白搭。<br> + 反过来,如果开发者深谙简化、语义和性能等的意义与价值,对不同的方案有全面的了解,即使使用 JavaScript 在前端生成网页,最终也可以产生各方面表现良好的页面。虽然,多数情况下,这样的开发者往往会保留页面的基本框架,不由 JS 生成。</p> +</div> + +<p>在其他情况中,不同浏览器对新特性的支持不同,也可能会使某些功能和样式对某些用户不起作用。除此之外,响应式设计问题也很常见——在桌面浏览器中看起来不错的网站可能会在移动设备上给出可怕的体验,因为内容太小而无法阅读,或者性能被代价昂贵的动画严重拖累。</p> + +<p>让我们前进吧,看看我们如何能够减少 HTML / CSS 中的跨浏览器错误。</p> + +<h2 id="首先:解决一般问题">首先:解决一般问题</h2> + +<p>我们在本系列的<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction#Testingdiscovery">第一篇文章</a>中说过,一个好的策略是在桌面/移动设备上测试几个现代的浏览器,以确保您的代码正常工作,然后才能专注于跨浏览器问题。</p> + +<p>在我们的<a href="/zh-CN/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">调试 HTML</a> 和<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">调试 CSS</a> 两篇文章中,我们也提供了一些关于调试 HTML/CSS 的基本指导——如果您不熟悉基础知识,那么在继续之前,您得学习这些文章。</p> + +<p>基本上,这是一个如何检查您的 HTML 和 CSS 代码,是否格式正确、不包含任何语法错误的问题。</p> + +<div class="blockIndicator note"> +<p>注意:CSS 和 HTML 之间的一个常见问题,就是不同的 CSS 规则之间发生冲突。当您使用第三方代码时,问题可能大到难以修补。例如,你可能正用着一个 CSS 框架,并发现它使用的一个类名与你已经用于某处的类名冲突。或者,您可能会发现,由某种第三方 API(例如生成广告横幅的脚本)生成的 HTML 包含您已经用于别处的类名称或者 ID。<br> + 为了确保不会发生这种情况,您需要首先研究一下您正在使用的工具,了解它们可能使用的类名与 ID,并围绕,或者规避着它们来设计您的代码。设法为 CSS 加上“命名空间”也是值得的,例如,如果有一个小部件,就先确保它有一个独特的类,然后基于这个类来选择元件内的这个类的选择器,这样一来,冲突的可能性就小下来了。例如 .audio-player ul a。</p> +</div> + +<h3 id="验证">验证</h3> + +<p>对于 HTML,要确保所有的标签都被正确地关闭和嵌套,写好 DOCTYPE,而且正确地使用各种标签。一个好的策略是定期验证你的代码。一个可以做到这一点的<a href="https://validator.w3.org/">服务</a>是 W3C<a href="https://validator.w3.org/"> 标记验证服务</a>,提供您的代码,它就能返回错误列表:<img alt="The HTML validator homepage" src="https://mdn.mozillademos.org/files/12441/validator.png"></p> + +<p>CSS 有一个类似的路线——你需要检查你的属性名拼写是否正确,属性值拼写正确,并且对于它们使用的属性是有效的,你不会丢失任何大括号,等等。为了这个目的,W3C也有一个 <a href="http://jigsaw.w3.org/css-validator/">CSS 验证器</a>。</p> + +<h3 id="Linter">Linter</h3> + +<p>另一个很好的选择就是所谓的 Linter 程序,它不仅可以指出错误,还可以标记关于 CSS 中不良做法的警告,以及其他一些要点。大家可以定制 Linter,以得到更严格或者更宽松的错误/警告报告。</p> + +<p>有许多在线 Linter 程序,其中最好的可能是“脏标记”<a href="https://www.dirtymarkup.com/">Dirty Markup</a>(HTML,CSS,JavaScript)和 <a href="http://csslint.net/">CSS Lint</a>(仅限CSS)。它们可以让你把代码粘贴到一个窗口中,并且会用十字标记任何错误,然后可以将它们悬停,以获得错误信息,通知你问题是什么。肮脏的标记还允许您使用清理按钮修复您的标记。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14113/dirty-markup.png" style="border-style: solid; border-width: 1px; display: block; height: 203px; margin: 0px auto; width: 1204px;"></p> + +<p>但是,将代码复制并粘贴到网页上以检查其有效性是不太方便的。你真正想要的 Linter 可以和你的工作流更加契合,减免一切窗口切换等麻烦。</p> + +<p>许多代码编辑器都有 linter 插件。Github 的<a href="https://atom.io/"> Atom</a> 代码编辑器例如有一个丰富的插件生态系统可用,有很多 linting 选项。向您展示一个这样的插件通常如何工作的例子:</p> + +<ol> + <li>安装 Atom(如果您还没有安装最新版本),请从上面链接的 Atom 页面下载。</li> + <li>转到 Atom 的<em>首选项...</em>对话框(例如在 Mac 上选择 <em>Atom > Preferences...</em> 或在 Windows / Linux 上选择 <em>File > Preferences...</em>),然后在左侧菜单中选择Install选项。</li> + <li>在搜索包文本字段中,输入“lint”,然后按 Enter / 回车键搜索与 lint 相关的包。</li> + <li>您应该在列表顶部看到一个名为 lint 的包。首先安装它(使用安装按钮),因为其他短裤依靠它工作。之后,安装用于linting CSS 的 linter-csslint 插件,和用于 lint HTML 的linter-tidy 插件。</li> + <li>软件包完成安装后,尝试加载一个 HTML 文件和一个 CSS 文件:在行号旁边会看到用绿色(警告)和红色(错误)圆圈,突出显示任何存在的问题,底部则提供行号、错误消息,有时有建议的值或者其他修复。</li> +</ol> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14109/atom-htmltidy.png" style="display: block; margin: 0 auto;"><img alt="" src="https://mdn.mozillademos.org/files/14107/atom-csslint.png" style="display: block; height: 516px; margin: 0px auto; width: 700px;"></p> + +<p>其他受欢迎的编辑器也有类似的 linting 包可用。例如:</p> + +<ul> + <li><a href="www.sublimelinter.com/">SublimeLinter</a> for Sublime Text</li> + <li><a href="https://sourceforge.net/projects/notepad-linter/">Notepad++ linter</a></li> + <li><a href="https://marketplace.visualstudio.com/search?target=vscode&category=Linters&sortBy=Installs">VSCode linter</a></li> +</ul> + +<h3 id="浏览器开发者工具">浏览器开发者工具</h3> + +<p>大多数浏览器中内置的开发人员工具还提供了有用的工具来查找错误,主要是针对CSS 的。</p> + +<div class="note"> +<p><strong>注意</strong>:在开发工具中,HTML 错误不会直接显示,因为浏览器会尝试自动纠正错误形成的标记;目前,W3C 验证器是获取 HTML 错误的最佳方法——请参阅上面的<a href="https://developer.mozilla.org/zh-CN/docs/preview-wiki-content#Validation">验证</a>。</p> +</div> + +<p>例如,在 Firefox 中,CSS 检查器将显示未应用的 CSS 声明,并带有警告三角形。悬停警告三角将提供一个描述性的错误信息:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14111/css-message-devtools.png" style="display: block; height: 311px; margin: 0px auto; width: 451px;"></p> + +<p>其他浏览器的开发者工具有相似的功能。</p> + +<h2 id="常见的跨浏览器问题">常见的跨浏览器问题</h2> + +<p>现在让我们继续看看一些最常见的跨浏览器 HTML 和 CSS 问题。这里,我们重点先看的方面是一些浏览器缺乏对现代功能和布局的支持所造成的问题。</p> + +<h3 id="旧版浏览器不支持新特性">旧版浏览器不支持新特性</h3> + +<p>这是一个常见的问题,尤其是当您需要支持旧的浏览器(例如旧的IE版本),或者您正在使用使用CSS前缀实现的功能时。通常,大多数核心HTML和CSS功能(如基本的HTML元素,CSS基本颜色和文本样式)在大多数浏览器中都可以使用,当您开始想要使用 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a>,<a href="/zh-CN/docs/Web/Apps/Fundamentals/Audio_and_video_delivery">HTML5 视频/音频</a>,甚至更新的 <a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Grids#Native_CSS_Grids_with_Grid_Layout">CSS Grid</a> 或 <code><a href="/zh-CN/docs/Learn/CSS/Styling_boxes/Advanced_box_effects#-webkit-background-clip_text">-webkit-background-clip:text</a></code> 等新功能时,会发现更多的问题。</p> + +<p>一旦您确定了您将要使用的潜在问题技术列表,研究它们所支持的浏览器以及相关技术是否有用是一个不错的主意。请参阅下面的<a href="https://developer.mozilla.org/zh-CN/docs/preview-wiki-content#Finding_help">查找帮助</a>。</p> + +<h4 id="HTML_回退行为">HTML 回退行为</h4> + +<p>一些问题可以通过利用 HTML / CSS 的自然工作方式来解决。</p> + +<p>无法识别的 HTML 元素被浏览器视为匿名内联元素(有效内联元素,没有语义值,类似于 {{htmlelement("span")}} 元素)。您还可以参考他们通过自己的名字,并用CSS样式它们,例如-你只需要确保它们表现为你想让他们,例如对所有新的语义元素设置 <code>display: block;</code>(如 {{htmlelement("article")}}、{{htmlelement("aside")}} 等),但现在,基本只有在旧版本的 IE 不能识别他们(IE 8 和更低)。这样,新的浏览器就可以正常使用这些代码,但是旧的IE版本也可以对这些元素进行样式化。</p> + +<div class="blockIndicator note"> +<p><strong>注意:</strong>请参阅<a href="https://developer.mozilla.org/zh-CN/docs/preview-wiki-content#IE_conditional_comments">IE条件注释</a>以获取最佳实践。</p> +</div> + +<p>更复杂的元素,如HTML <code><a href="https://wiki.developer.mozilla.org/en-US/docs/Web/HTML/Element/video"><video></a></code>、<code><a href="/zh-CN/docs/Web/HTML/Element/audio"><audio></a></code>,和 <code><a href="/zh-CN/docs/Web/HTML/Element/canvas"><canvas></a></code>(和其他功能除外)有自然的fallback加入机制,其工作原理与上述相同。您可以在开始和结束标记之间添加回退内容,非支持的浏览器将有效地忽略外部元素并运行嵌套的内容。</p> + +<p>例如:</p> + +<pre><video id="video" controls preload="metadata" poster="img/poster.jpg"> + <source src="video/tears-of-steel-battle-clip-medium.mp4" type="video/mp4"> + <source src="video/tears-of-steel-battle-clip-medium.webm" type="video/webm"> + <source src="video/tears-of-steel-battle-clip-medium.ogg" type="video/ogg"> + <!-- Flash fallback --> + <object type="application/x-shockwave-flash" data="flash-player.swf?videoUrl=video/tears-of-steel-battle-clip-medium.mp4" width="1024" height="576"> + <param name="movie" value="flash-player.swf?videoUrl=video/tears-of-steel-battle-clip-medium.mp4" /> + <param name="allowfullscreen" value="true" /> + <param name="wmode" value="transparent" /> + <param name="flashvars" value="controlbar=over&amp;image=img/poster.jpg&amp;file=flash-player.swf?videoUrl=video/tears-of-steel-battle-clip-medium.mp4" /> + <img alt="Tears of Steel poster image" src="img/poster.jpg" width="1024" height="428" title="No video playback possible, please download the video from the link below" /> + </object> + <!-- Offer download --> + <a href="video/tears-of-steel-battle-clip-medium.mp4">Download MP4</a> +</video></pre> + +<p>这个例子(来自<a href="/zh-CN/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/cross_browser_video_player">创建一个跨浏览器的视频播放器</a>)不仅包括旧的IE版本的Flash视频后备,而且还包括一个简单的链接,允许你下载视频,这样,即使 Flash 播放器无法运作,用户仍然可以访问视频。</p> + +<p>HTML5 表单元素也表现出了后备素质--HTML5 引入了一些特殊的 <code><a href="/zh-CN/docs/Web/HTML/Element/input"><input></a></code> 类型,用于将特定的信息输入到时间,日期,颜色,数字等形式中。这些非常有用,特别是在移动平台上,它提供了一种无痛苦的输入数据对用户体验非常重要。当使用这些输入类型时,支持平台提供特殊的UI小部件,例如用于输入日期的日历小部件。</p> + +<p>以下示例显示日期和时间输入:</p> + +<pre class="brush: html"><form> + <div> + <label for="date">Enter a date:</label> + <input id="date" type="date"> + </div> + <div> + <label for="time">Enter a time:</label> + <input id="time" type="time"> + </div> +</form></pre> + +<p>此代码的输出如下所示:</p> + +<div class="hidden"> +<h6 id="Hidden_example">Hidden example</h6> + +<pre class="brush: css">label { + float: left; + width: 30%; + text-align: right; + } + + input { + float: right; + width: 65%; + } + + label, input { + margin-bottom: 20px; + } + + div { + clear: both; + margin: 10px; + } + + body { + width: 400px; + margin: 0 auto; + }</pre> + +<pre class="brush: html"><form> + <div> + <label for="date">Enter a date:</label> + <input id="date" type="date"> + </div> + <div> + <label for="time">Enter a time:</label> + <input id="time" type="time"> + </div> + </form></pre> +</div> + +<p>{{ EmbedLiveSample('Hidden_example', '100%', 150) }}</p> + +<div class="note"> +<p>注意:您也可以在GitHub上以<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/html-css/forms-test.html">forms-test.html的形式直接</a>看到这个运行(参见<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/html-css/forms-test.html">源代码</a>)。</p> +</div> + +<p>如果您在支持的浏览器(如桌面/ Android Chrome或iOS Safari)上查看该示例,则会在尝试输入数据时看到特殊的小部件/功能。在一个非支持的平台上,例如Firefox或Internet Explorer,输入只会回退到正常的文本输入,所以至少用户仍然可以输入一些信息。</p> + +<p>注意:当然,这对于您的项目需求来说可能不是一个很好的解决方案 - 视觉呈现方面的差异并不是很大,而且更难保证数据将以您希望的格式输入。对于跨浏览器表单,依靠简单的表单元素,或者选择性地使用高级表单元素来支持浏览器,或者使用提供体面的跨浏览器表单小部件(如 <a href="http://jqueryui.com/">jQuery UI</a> 或 <a href="https://bootstrap-datepicker.readthedocs.io/en/latest/">Bootstrap datepicker</a>)的库可能会更好。</p> + +<h4 id="CSS_回退行为">CSS 回退行为</h4> + +<p>CSS 的情况可以说比 HTML 更好一些。如果一个浏览器遇到一个它不明白的声明或规则,它只会完全跳过它,而不会强行应用它或者抛出错误。这可能会让你和你的用户感到沮丧,如果这样的错误滑入产品代码,但至少意味着整个网站不会因为一个错误而崩溃,如果巧妙地使用,你可以使用它。</p> + +<p>让我们来看一个例子——一个简单的 CSS 风格的框,它有一些 CSS3 特性提供的样式:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14141/blingy-button.png" style="display: block; margin: 0 auto;"></p> + +<div class="note"> +<p><strong>注意</strong>:你也可以在 GitHub 上浏览这个例子:<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/html-css/button-with-fallback.html">button-with-fallback.html 的形式运行</a>(另见<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/html-css/button-with-fallback.html">源代码</a>)。</p> +</div> + +<p>该按钮有一些样式声明,但我们最感兴趣的两个如下:</p> + +<pre class="brush: css">button { + ... + + background-color: #ff0000; + background-color: rgba(255,0,0,1); + box-shadow: inset 1px 1px 3px rgba(255,255,255,0.4), + inset -1px -1px 3px rgba(0,0,0,0.4); +} + +button:hover { + background-color: rgba(255,0,0,0.5); +} + +button:active { + box-shadow: inset 1px 1px 3px rgba(0,0,0,0.4), + inset -1px -1px 3px rgba(255,255,255,0.4); +}</pre> + +<p>在这里,我们提供了一个 <a href="/zh-CN/docs/Web/CSS/color_value#rgba()">RGBA</a> <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/background-color" title='CSS属性中的 background-color 会设置元素的背景色, 属性的值为颜色值或关键字"transparent"二者选其一.'>background-color</a>,改变悬停的不透明度,给用户一个暗示该按钮是交互式的提示,以及一些半透明的嵌入式 <a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/box-shadow" title="此页面仍未被本地化, 期待您的翻译!">box-shadow</a> 阴影,使按钮有一点纹理和深度。问题在于 RGBA 颜色和方块阴影在9以前的IE版本中不起作用 - 在较旧的版本中,背景根本不会显示出来,所以文本将是不可读的,这没有任何好处!</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14135/unreadable-button.png" style="display: block; margin: 0 auto;"></p> + +<p>为了解决这个问题,我们添加了第二个background-color声明,它只是指定了一个十六进制颜色 - 这在旧版本的浏览器中得到了支持,并且在现代闪亮功能无法使用的情况下作为后备。访问此页面的浏览器首先应用第一个background-color值,当它到达第二个background-color声明时,如果它支持RGBA颜色,它将用这个值覆盖初始值。如果不是的话,它会忽略整个声明,继续前进。</p> + +<div class="note"> +<p><strong>注意</strong>:这同样适用于其他 CSS 功能,如真正的<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries">媒体查询</a>,<a href="/zh-CN/docs/Web/CSS/@font-face">@font-face</a> 以及 <a href="/zh-CN/docs/Web/CSS/@supports">@supports</a> 块-如果不支持他们,浏览器只是忽略它们。</p> +</div> + +<h4 id="IE_条件注释">IE 条件注释</h4> + +<p>IE条件注释是修改后的专有HTML注释语法,可用于将HTML代码有选择地应用于不同版本的IE。这已被证明是一个非常有效的机制来解决跨浏览器的错误。语法如下所示:</p> + +<pre><!--[if lte IE 8]> + <script src="ie-fix.js"></script> + <link href="ie-fix.css" rel="stylesheet" type="text/css"> +<![endif]--></pre> + +<p>只有当浏览器查看页面是IE 8或更旧时,此块才会应用IE特定的CSS和JavaScript。lte意思是“小于或等于”,但是也可以使用lt、gt、gte、!(NOT) 以及其他逻辑语法。</p> + +<div class="note"> +<p><strong>注意:</strong>Sitepoint 的 <a href="https://www.sitepoint.com/web-foundations/internet-explorer-conditional-comments/">Internet Explorer条件注释</a>提供了一个有用的初学者教程/参考,详细解释了条件注释语法。</p> +</div> + +<p>正如你所看到的,这对于将代码修复应用于旧版本的IE尤其有用。我们前面提到的用例(使旧版本的IE可以使用现代语义元素)可以通过使用条件注释来轻松实现,例如,您可以在IE样式表中添加类似这样的内容:</p> + +<pre class="brush: css">aside, main, article, section, nav, figure, figcaption { + display: block; +}</pre> + +<p>然而,这并不是那么简单 - 您还需要通过 JavaScript 在 DOM 中创建您想要样式化的每个元素的副本,让它们变得可以风格化; 这是一个奇怪的怪癖,我们不会在这里让你知道细节。例如:</p> + +<pre class="brush: js">const asideElem = document.createElement('aside'); + ...</pre> + +<p>这听起来像是一个痛苦的处理,但幸运的是有一个 <a href="/zh-CN/docs/Glossary/polyfill" title="polyfill:polyfill是一段代码(通常是Web上的JavaScript),用于在不支持本地支持的旧浏览器上提供现代功能。">polyfill</a> 可以为您做必要的修复,除此之外 - 请参阅 <a href="https://github.com/aFarkas/html5shiv">HTML5Shiv</a> 了解所有细节(请参阅<a href="https://github.com/aFarkas/html5shiv#installation">手动安装</a>以获取最简单的用法)。</p> + +<h4 id="选择器支持">选择器支持</h4> + +<p>当然,如果你不使用正确的<a href="/zh-CN/docs/Learn/CSS/Introduction_to_CSS/Selectors">选择器</a>来选择你想要的样式的话,就没有任何CSS功能可以应用!如果您只是错误地写了一个选择器,所以在任何浏览器中的样式都不像预期的那样,您只需要排除故障并找出选择器出了什么问题。我们发现使用浏览器的开发工具来检查你试图设计的元素是有帮助的,然后看一下DOM检查器倾向于提供的DOM树面包屑跟踪,看看你的选择器是否比它有意义。</p> + +<p>例如,在 Firefox 开发工具中,您可以在DOM检查器的底部获得这种输出:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14139/dom-breadcrumb-trail.png" style="display: block; height: 24px; margin: 0px auto; width: 448px;"></p> + +<p>例如,如果您尝试使用此选择器,则可以看到它不会根据需要选择输入元素:</p> + +<pre class="brush: css">form > #date</pre> + +<p>(date表单输入不是直接在里面<form>;你最好使用一般的后代选择器而不是子选择器)。</p> + +<p>然而,出现在IE 9比旧的版本的另一个问题是,没有任何新的选择(主要是伪类和伪元素如 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:nth-of-type">:nth-of-type</a>,<a href="/zh-CN/docs/Web/CSS/:not">:not</a>,<a href="/zh-CN/docs/Web/CSS/::selection">::selection</a>,等)的工作。如果你想在你的 CSS 中使用这些,并且需要支持较老的IE版本,那么一个好的方法就是使用Keith Clark 的 <a href="http://selectivizr.com/">Selectivizr</a> 库 - 这是一个小型的 JavaScript 库,可以在现有的 JavaScript 库(如 <a href="http://jquery.com/">jQuery</a> 或 <a href="http://mootools.net/">MooTools</a>)上工作。</p> + +<ol> + <li>要尝试此示例,请创建 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/html-css/selectivizr-example-start.html">selectivizr-example-start.html</a> 的本地副本。如果你看看这个现场直播,你会发现它包含两个段落,其中一个是风格。我们已经选择了这个段落 <code>p:first-child</code>,这在老版本的 IE 中不起作用。</li> + <li>现在下载 <a href="http://mootools.net/">MooTools</a>和<a href="http://selectivizr.com/">Selectivizr</a>,并将它们保存在与示例 HTML 相同的目录中。</li> + <li>将下面的代码放到 HTML 文档的开头,就在开始 <code><style></code> 标记之前: + <pre><script type="text/javascript" src="MooTools-Core-1.6.0.js"></script> + <!--[if (gte IE 6)&(lte IE 8)]> + <script type="text/javascript" src="selectivizr-min.js"></script> + <![endif]--></pre> + </li> +</ol> + +<p>如果你尝试在旧版本的IE中运行它,它应该可以正常工作。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14137/new-selector-ie7.png" style="border-style: solid; border-width: 1px; display: block; height: 516px; margin: 0px auto; width: 771px;"></p> + +<h4 id="处理_CSS_前缀">处理 CSS 前缀</h4> + +<p>CSS 前缀带来的另外一个问题是 - 这些是一种用于允许浏览器供应商在技术处于试验状态时实现自己版本的 CSS(或 JavaScript)特性的机制,所以他们可以使用它来获取它没有与其他浏览器的实现冲突,或者最终的前置实现。举个例子:</p> + +<ul> + <li>Mozilla 使用 <code>-moz-</code></li> + <li>Chrome / Opera / Safari 使用 <code>-webkit-</code></li> + <li>微软使用 <code>-ms-</code></li> +</ul> + +<p>这里有一些例子:</p> + +<pre class="brush: css">-webkit-transform: rotate(90deg); + +background-image: -moz-linear-gradient(left,green,yellow); +background-image: -webkit-gradient(linear,left center,right center,from(green),to(yellow)); +background-image: linear-gradient(to right,green,yellow); +</pre> + +<p>第一行显示了一个<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform">transform</a>带有-webkit-前缀的属性- 这是在Chrome中使用变换功能所必需的,直到功能定稿,并且这些浏览器添加了该属性的前缀无版本(在撰写本文时,Chrome支持这两个版本)。</p> + +<p>最后三行显示了三个不同版本的<a href="/zh-CN/docs/Web/CSS/linear-gradient">linear-gradient()</a>函数,用于在元素的背景中生成线性渐变:</p> + +<ol> + <li>第一个有一个-moz-前缀,并显示一个略老的语法版本(Firefox)</li> + <li>第二个有一个-webkit-前缀,并显示一个更老的专有版本的语法(这实际上是从一个真正的旧版本的WebKit引擎)。</li> + <li>第三个没有前缀,并显示语法的最终版本(包含在定义此功能的<a href="https://drafts.csswg.org/css-images-3/#linear-gradients">CSS图像值和替换内容模块级别3规范中</a>)。</li> +</ol> + +<p>前缀功能从来不应该用于生产网站——它们可能会在没有警告的情况下更改或删除,并导致跨浏览器问题。当开发人员决定只使用-webkit- 某个属性的版本时,这是一个特别的问题- 这意味着该网站在其他浏览器中不起作用。这实际上发生了很多,其他浏览器已经开始实现-webkit-各种CSS属性的前缀版本,所以他们将使用这样的代码。由于这些类型的问题,浏览器供应商使用前缀最近已经下降,但仍然有一些需要注意。</p> + +<p>如果您坚持使用前缀功能,请确保使用正确的功能。您可以在MDN参考页面上查找哪些浏览器需要前缀,以及像<a href="http://caniuse.com/">caniuse.com</a>这样的<a href="http://caniuse.com/">站点</a>。如果你不确定,你也可以直接在浏览器中做一些测试。</p> + +<p>试试这个简单的例子:</p> + +<ol> + <li>打开google.com或具有突出标题或其他块级元素的其他站点。</li> + <li>右键/ Cmd +单击有问题的元素并选择Inspect / Inspect元素(或者浏览器中的任何选项) - 这应该在浏览器中打开开发工具,在DOM检查器中高亮显示该元素。</li> + <li>寻找可以用来选择该元素的功能。例如,在撰写本文时,主要的Google徽标的ID为hplogo。</li> + <li>将对此元素的引用存储在变量中,例如: + <pre class="brush: js">const test = document.getElementById('hplogo');</pre> + </li> + <li>现在尝试为您感兴趣的CSS属性设置一个新的值,你可以使用元素的<a href="/zh-CN/docs/Web/API/HTMLElement/style">style</a>属性来做到这一点,例如尝试在JavaScript控制台中输入这些: + <pre class="brush: js">test.style.transform = 'rotate(90deg)' +test.style.webkitTransform = 'rotate(90deg)'</pre> + </li> +</ol> + +<p>当您开始在第二个点之后键入属性名称表示形式(请注意,在JavaScript中,CSS属性名称是以较低的驼峰大小写,而不是连字符),JavaScript控制台应该开始自动填充浏览器中存在的属性的名称并匹配到目前为止所写的内容。这对于找出在该浏览器中实现哪个版本的属性很有用。</p> + +<p>在撰写本文时, Firefox 和 Chrome 都实现了 <code>-webkit-</code> 前缀和非前缀的<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform">transform</a>!</p> + +<p>一旦你找到了你需要支持的前缀,你应该把它们写在你的CSS中,例如:</p> + +<pre class="brush: css">-ms-transform: rotate(90deg); +-webkit-transform: rotate(90deg); +transform: rotate(90deg);</pre> + +<p>这可以确保所有支持上述任何属性的浏览器都可以使该功能正常工作。值得把非前缀版本放在最后,因为那将是最新的版本,如果可能的话,你会希望浏览器使用它。例如,如果浏览器实现了-webkit-版本和非前缀版本,则它将首先应用-webkit-版本,然后用非前缀版本覆盖它。你希望这样发生,而不是相反。</p> + +<p>当然,这样做很多不同的CSS规则可能会变得非常乏味。最好使用自动化工具来为你做。而这样的工具存在:</p> + +<p><a href="http://leaverou.github.io/prefixfree/">无前缀的 JavaScript 库</a>可以附加到页面上,并自动检测浏览器查看页面的功能,并根据需要添加前缀。使用起来非常简单方便,虽然它有一些缺点(请参阅上面的链接了解详细信息),但有一点是可争议的,即在您的站点中解析每个样式表并在运行时添加前缀可能会消耗计算机的处理能力为一个大的网站。</p> + +<p>另一个解决方案是在开发过程中自动添加前缀,而这个(和其他的东西除外)可以使用像<a href="https://github.com/postcss/autoprefixer">Autoprefixer</a>和<a href="http://postcss.org/">PostCSS</a>这样的工具完成。这些工具可以以多种方式使用,例如Autoprefixer有一个<a href="http://autoprefixer.github.io/">在线版本</a>,允许您在左侧输入非前缀CSS,并在右侧给出前缀添加版本。您可以使用<a href="https://github.com/postcss/autoprefixer#options">Autoprefixer选项中</a>列出的符号来选择要确保支持的浏览器; 另请参阅基于此的<a href="https://github.com/ai/browserslist#queries">Browserslist查询</a>以获取更多详细信息。例如,以下查询将选择所有主流浏览器的最后2个版本和 IE9 以上的版本。</p> + +<pre>last 2 versions, ie > 9</pre> + +<p>Autoprefixer 也可用于其他更便捷的方式 - 请参阅<a href="https://github.com/postcss/autoprefixer#usage">Autoprefixer使用情况</a>。例如,您可以使用任务运行器/构建工具(如<a href="http://gulpjs.com/">Gulp</a>或<a href="https://webpack.github.io/">Webpack)</a>在开发完成后自动添加前缀。(解释这些工作如何超出本文的范围。)</p> + +<p>您也可以使用插件来编辑文本编辑器,如Atom或Sublime文本。例如,在Atom中:</p> + +<ol> + <li>您可以通过转到首选项>安装,搜索Autoprefixer,然后安装。</li> + <li>您可以通过按“Autoprefixer 设置”按钮并在页面的“设置”部分的文本字段中输入查询来设置浏览器查询。</li> + <li>在你的代码中,你可以选择要添加前缀的CSS部分,打开命令调色板(Cmd / Ctrl + Shift + P),然后输入Autoprefixer并选择Autoprefixer自动完成的结果。</li> +</ol> + +<p>作为一个例子,我们输入了下面的代码:</p> + +<pre class="brush: css">body { + display: flex; +}</pre> + +<p>我们突出显示了它,并运行 Autoprefixer 命令,并用以下代码替换它:</p> + +<pre class="brush: css">body { + display: -webkit-box; + display: -ms-flexbox; + display: flex; +}</pre> + +<h3 id="布局问题">布局问题</h3> + +<p>另一个可能出现的问题是浏览器之间布局的差异。从历史上看,这往往是一个更大的问题,但是最近,现代浏览器倾向于更一致地支持CSS,布局问题往往更倾向于:</p> + +<ul> + <li>对现代布局功能缺乏(或不同)支持。</li> + <li>布局在移动浏览器中看起来不太好(即响应式设计问题)。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:过去,Web 开发人员使用 reset CSS 删除应用于HTML的所有默认浏览器样式,然后将自己的样式应用于顶部的所有样式 - 这样做是为了使项目的样式更加一致,并减少可能的跨浏览器问题,特别是像布局的东西。然而现在它被认为过头了。我们现在最好的等价物是<a href="https://necolas.github.io/normalize.css/">normalize.css</a>,这是一个整齐的CSS,在默认的浏览器样式上略微增加一点,使事情更加一致,并解决一些布局问题。建议您将normalize.css应用于所有的HTML页面。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>:当试图追踪一个棘手的布局问题,一个好方法是添加一个明亮的颜色<a href="https://developer.mozilla.org/zh-CN/docs/Web/CSS/outline" title="CSS的outline属性是用来设置一个或多个单独的轮廓属性的简写属性 , 例如 outline-style, outline-width 和 outline-color。 多数情况下,简写属性更加可取和便捷。">outline</a>到有问题的元素,或附近的所有元素。这使得更容易看到一切放置在哪里。请参阅<a href="http://www.otsukare.info/2016/10/05/debugging-css" rel="bookmark" title="通过大纲可视化调试您的CSS的永久连接。">使用大纲可视化调试您的CSS以</a>获取更多详细信息。</p> +</div> + +<h4 id="支持新的布局特性">支持新的布局特性</h4> + +<p>今天网络上的大部分布局工作都是使用<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Floats">浮动工具</a>完成的- 这是因为浮动支持得很好(可以回到IE4,尽管如此,如果您尝试支持IE,也需要调查一些错误很久以前)。但是,它们并不是真正用于布局的目的 - 使用浮动的方式实际上是一种黑客攻击 - 而且它们有一些严重的限制(例如,请参阅<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox#Why_Flexbox">为什么选择Flexbox?</a>)</p> + +<p>最近,出现了专门的布局机制,例如<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a>和<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Grids#Native_CSS_Grids_with_Grid_Layout">CSS Grids</a>,这些布局机制使常见的布局任务变得更加容易并消除了这些缺点。但是,这些在浏览器中并没有得到很好的支持:</p> + +<ul> + <li>CSS Grid 是非常新的;在撰写本文的时候,他们只在最新版本的现代浏览器中才<a href="http://gridbyexample.com/browsers/">得到支持</a>。</li> + <li>Flexbox 在现代浏览器中<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox#Cross_browser_compatibility">得到</a>了<a href="/zh-CN/docs/Learn/CSS/CSS_layout/Flexbox#Cross_browser_compatibility">很好的支持</a>,但是在旧版浏览器中却存在问题。IE 9 根本不支持它,IE 10 和旧版本的 iOS /桌面 Safari 分别支持 Flexbox 规范的两种不兼容老版本。如果您想尝试在所有这些浏览器上使用 flexbox(请参阅<a href="https://dev.opera.com/articles/advanced-cross-browser-flexbox/">高级跨浏览器Flexbox</a>以获得创意),<a href="https://dev.opera.com/articles/advanced-cross-browser-flexbox/">则会</a>导致一些有趣的浏览器前缀<a href="https://dev.opera.com/articles/advanced-cross-browser-flexbox/">杂乱</a>。</li> +</ul> + +<p>布局功能并不像简单的颜色,阴影或渐变那样容易提供优雅的后备。如果布局属性被忽略,你的整个设计可能会崩溃。因此,您需要使用功能检测来检测访问的浏览器是否支持这些布局功能,并根据结果有选择地应用不同的布局(我们将在后面的文章中详细介绍功能检测)。</p> + +<p>例如,您可以将 Flexbox 布局应用于现代浏览器,然后将浮动布局应用于不支持 Flexbox 的旧浏览器。</p> + +<div class="note"> +<p><strong>注意</strong>:CSS 中还有一个相当新的特性 <a href="/zh-CN/docs/Web/CSS/@supports">@supports</a>,它允许你实现原生特征检测测试。</p> +</div> + +<h4 id="响应式设计问题">响应式设计问题</h4> + +<p>响应式设计是创建网页布局以适应不同设备形式因素(例如不同的屏幕宽度,方向(纵向或横向)或分辨率)的做法。例如桌面布局在移动设备上看起来会很糟糕,所以您需要使用<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries">媒体查询</a>来提供合适的移动布局,并确保使用<a href="/zh-CN/docs/Mozilla/Mobile/Viewport_meta_tag">视口</a>正确应用它。您可以在<a href="/zh-CN/docs/Web/Apps/Progressive/Responsive/responsive_design_building_blocks">响应式设计的构建模块中</a>找到这些实践的详细说明。</p> + +<p>解决方案也是一个大问题 - 例如,移动设备不太可能需要比台式电脑大的图像,并且更可能具有较慢的互联网连接,并且甚至可能使昂贵的数据计划浪费带宽成为更多的问题。另外,不同的设备可以具有一系列不同的分辨率,这意味着较小的图像可以出现像素化。有许多技术可以让您解决这些问题,从简单的<a href="https://developer.mozilla.org/en-US/Apps/Progressive/Responsive/Mobile_first">移动第一媒体查询</a>到更复杂的<a href="/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images#Resolution_switching_Different_sizes">响应式图像技术</a>。</p> + +<p>可能出现问题的另一个困难是浏览器对使上述技术成为可能的特征的支持。媒体查询没有在IE 8或更少的支持,所以如果你想使用移动优先布局和具有桌面布局则适用于旧版本的 IE,你将有一个媒体查询应用于<a href="/zh-CN/docs/Glossary/polyfill" title="polyfill:polyfill是一段代码(通常是Web上的JavaScript),用于在不支持本地支持的旧浏览器上提供现代功能。">填充工具</a>到您的网页,如 <a href="https://code.google.com/archive/p/css3-mediaqueries-js/">css3- mediaqueries-js</a> 或 <a href="https://github.com/scottjehl/Respond">Respond.js</a>。</p> + +<h2 id="寻找帮助">寻找帮助</h2> + +<p>HTML 和 CSS 还有很多其他的问题。最重要的是如何在网上找到答案。</p> + +<p>其中最好的支持信息来源是Mozilla开发者网络(你现在就在这!),<a href="http://stackoverflow.com/">stackoverflow.com</a> 和 <a href="http://caniuse.com/">caniuse.com</a>。</p> + +<p>为使用 Mozilla 开发者网络(MDN),大多数人会搜索他们正在尝试查找信息的技术,以及术语“mdn”,例如“mdn HTML5 video”。MDN 包含几种有用的内容类型:</p> + +<ul> + <li>带有客户端网络技术浏览器支持信息的参考资料,例如<a href="/zh-CN/docs/Web/HTML/Element/video"><video> 参考页面</a>。</li> + <li>其他支持的参考资料,例如 <a href="/zh-CN/docs/Web/HTML/Supported_media_formats">HTML 音频和视频元素支持的媒体格式</a>。</li> + <li>解决特定问题的有用教程,例如<a href="/zh-CN/docs/Web/Apps/Fundamentals/Audio_and_video_delivery/cross_browser_video_player">创建跨浏览器的视频播放器</a>。</li> +</ul> + +<p><a href="http://caniuse.com/">caniuse.com</a> 提供支持信息,以及一些有用的外部资源链接。例如,请参阅<a href="http://caniuse.com/#search=video">http://caniuse.com/#search=video</a>(您只需在文本框中输入要搜索的功能)。</p> + +<p><a href="http://stackoverflow.com/">stackoverflow.com</a>(SO)是一个论坛网站,您可以提出问题,并让开发人员分享他们的解决方案,查看以前的帖子,并帮助其他开发人员。在发布新问题之前,建议您先看看是否已经回答了您的问题。例如,我们在SO上搜索了“cross browser html5 video”,并且很快得到了<a href="http://stackoverflow.com/questions/16212510/html5-video-with-full-cross-browser-compatibility">具有完全跨浏览器兼容性的 HTML5 Video</a>。</p> + +<p>除此之外,请尝试使用您最喜爱的搜索引擎来解决您的问题。如果有的话,搜索特定的错误信息通常很有用 - 其他开发人员可能会遇到与您一样的问题。</p> + +<h2 id="总结">总结</h2> + +<p>现在您应该熟悉 Web 开发中主要的跨浏览器 HTML 和 CSS 问题,以及如何解决这些问题。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies","Learn/Tools_and_testing/Cross_browser_testing/JavaScript", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">Introduction to cross browser testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">Strategies for carrying out testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">Handling common HTML and CSS problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">Handling common accessibility problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Implementing feature detection</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">Introduction to automated testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Setting up your own test automation environment</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/index.html new file mode 100644 index 0000000000..816fd36319 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/index.html @@ -0,0 +1,43 @@ +--- +title: 跨浏览器测试 +slug: Learn/Tools_and_testing/Cross_browser_testing +tags: + - CSS + - HTML + - JavaScript + - 初学者 + - 工具 + - 易用性 + - 测试 + - 自动化 + - 跨浏览器 +translation_of: Learn/Tools_and_testing/Cross_browser_testing +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">该模块侧重于跨浏览器测试web项目。我们会查看您的目标受众(例如,您最需要担心的用户,浏览器和设备),如何进行测试最主要的问题是您会遇到不同类型的代码和怎么缓解他们,什么工具对于你测试和解决问题是最有帮助的,以及如何使用自动化来加速测试。</p> + +<h2 id="先决条件">先决条件</h2> + +<p> 在你尝试使用这里详细介绍的工具时,你需要已经学习核心<a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</p> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></dt> + <dd>本文通过提供跨浏览器测试主题的概述,回答诸如“什么是跨浏览器测试?”,“您将遇到的最常见类型的问题是什么?”和“什么是测试,识别和解决问题的主要方法?“</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">测试策略</a></dt> + <dd>接下来,我们深入了解测试,寻找目标受众(例如:你需要确定测试的浏览器,设备和其他部分),低成本测试策略(必要时对各种设备和一些虚拟机进行adhoc测试),更高科技策略(自动化,使用专用测试apps)和用户组测试。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS"> 处理常见的HTML和CSS问题</a></dt> + <dd>现在,我们着重考虑你在HTML和CSS代码中可能遇到的一些常见的跨浏览器问题,以及那些工具可以用来防止问题发生和解决问题。这包括代码检查,处理CSS前缀,使用浏览器开发工具找出问题,使用polyfills添加浏览器支持,解决响应式问题等。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></dt> + <dd>现在我们来看看常见的跨浏览器JavaScript问题,以及怎么解决它们。这会包括使用浏览器开发工具跟踪和解决问题的信息,使用polyfills和库来解决问题,让现代的JavaScript特性在老浏览器中工作等等。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的可访问性问题</a></dt> + <dd>然后,我们把注意力转向可访问性上,提供常见问题的信息,怎么做简单的测试,和怎么使用审计/自动化工具找到可访问性问题。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">实现特征检查</a></dt> + <dd><code><font face="Open Sans, arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">特征检查包括确定浏览器是否支持确定的代码块,据此运行不同的代码,让浏览器能一直正常工作,而不是在一些浏览器上崩溃/报错。</span></font></code>本文详细介绍了愈合如何写简单的特征检查,怎么使用库来加速实现,和一些像<code>@supports</code>的本地特性来进行特征检查。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动测试简介</a></dt> + <dd>每天多次在几个浏览器和设备上手动运行可能会枯燥,浪费时间。有效处理这个问题,你需要熟悉自动化工具。在本文中,我们介绍可用的方法,怎么使用任务运行程序,以及学习怎么使用商业浏览器自动化测试工具如 Sauce Labs 和Browser Stack的基础知识。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">建立你自己的自动化测试环境</a></dt> + <dd>在本文中,我们会教你怎么安装你自己的自动化环境,并使用Selenium / WebDriver和测试库(如selenium-webdriver for Node)运行自己的测试。我们还将介绍如何将本地测试环境与商业应用程序集成在一起,正如上一篇文章所讨论的。</dd> +</dl> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/introduction/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/introduction/index.html new file mode 100644 index 0000000000..7b8772b4bd --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/introduction/index.html @@ -0,0 +1,207 @@ +--- +title: 跨浏览器测试介绍 +slug: Learn/Tools_and_testing/Cross_browser_testing/Introduction +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">本文是对跨浏览器测试的入门概述,帮助了解“什么是跨浏览器测试?”,“常见的问题都有哪些?”,以及“应该怎么测试,识别和修复问题?” </p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">阅读基础:</th> + <td>熟悉 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>了解跨浏览器测试的高级概念。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是跨浏览器测试?">什么是跨浏览器测试?</h2> + +<p>跨浏览器测试(cross browser testing)是确保您的网站或web应用能在可接受数量的浏览器(across an acceptable number of web browsers)上正常使用的测试方法。作为网站开发者,您有责任确保项目能供所有用户使用,无论他们使用的是哪种浏览器,设备或辅助工具。 您需要注意的点:</p> + +<ul> + <li>除了您在工作中经常使用的一两种浏览器,还有一些老旧的浏览器也会有用户在使用。这些浏览器对CSS和JavaScript的新特性支持的不够。</li> + <li>不同的设备支持的功能也不一样,有功能强大的新平板电脑,智能手机,智能电视,也有功能不全的廉价平板电脑,老旧手机。</li> + <li>残疾人士通过屏幕阅读器等辅助技术上网,可能不会使用鼠标(有些人只使用键盘)。</li> +</ul> + +<p>请记住,您不能代表产品的用户 - 您的网站能适配Macbook Pro或高端Galaxy Nexus,并不意味它适用于所有用户 - 还有很多测试工作要做!</p> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://hacks.mozilla.org/2016/07/make-the-web-work-for-everyone/">Make the web work for everyone</a> 文章列出了浏览器的市场份额,使用情况和相关兼容性问题。</p> +</div> + +<p>我们先解释下术语。首先,我们所讨论的“跨浏览器使用(working cross browser)”,应该在不同浏览器中提供可接受的用户体验。虽然无法在所有浏览器上提供相同的体验,但确保核心功能使用顺畅就算可以。比如在现代浏览器上,能显示动画、3D或闪光效果,而在较旧的浏览器上,可以呈现出相同信息的平面图片。只要网站主满意,你的工作就算完成了。</p> + +<p>另一方面,视力正常的用户能正常浏览内容,但视力障碍的用户却因为屏幕阅读器无法读取信息而无法阅读内容。这是糟糕的体验,需要您能兼容屏幕阅读器软件。</p> + +<p>其次,当我们说“可接受数量的浏览器(across an acceptable number of web browsers)” ,并不是说世界上100%的浏览器,这也是不可能。您可以通过信息收集了解用户都在使用哪些浏览器和设备,但也不能保证全都采集到(也是本专题第二篇所讨论的 — 参见 <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies#Gotta_test_%27em_all">要测试全部的吗?(Gotta test 'em all?)</a>)。作为web开发者,您自然要确保网站主要求的浏览器都能正常工作,但除此之外,您需要防御性编程(code defensively),尽可能让其它浏览器也能正常查看内容。 这是Web开发的重大挑战之一!</p> + +<div class="note"> +<p><strong>Note</strong>: 后面会详细介绍防御性编程(code defensively)</p> +</div> + +<h2 id="为什么会出现跨浏览器问题?">为什么会出现跨浏览器问题?</h2> + +<p>对于为何出现跨浏览器问题,原因有很多,而且要注意,我们在此讨论的是跨不同浏览器/设备/浏览偏好时出现的表现差异的问题。你应该在遇到跨浏览器问题之前就预先修复你代码里的Bug(如需巩固记忆,请参阅之前主题中的<a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">Debugging HTML</a>,<a href="https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">Debugging CSS</a>,以及<a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">What went wrong?Troubleshooting Javascript</a>)。</p> + +<p>跨浏览器问题会出现通常因为:</p> + +<ul> + <li>有时候浏览器有缺陷,或者实现功能的方式不同。这种情况比以前大有改观;在IE 4和Netscape 4争霸的二十世纪九十年代,浏览器厂商故意采用与其他厂商不同的方式开发功能,以此获得竞争优势,这使得开发者如陷地狱。近来各个浏览器更加注重遵循标准,但差异和缺陷仍会时而出现。</li> + <li> 一些浏览器对一些技术特点的支持力度与其他浏览器不同。当你在处理一些浏览器厂商正在实现的前沿技术点,或你必须支持一些很古老的不再更新维护的浏览器时,跨浏览器问题会不可避免的出现。举个例子:你想在自己网站里用到的一些前沿Javascript技术在旧版浏览器中可能不会生效。如果你需要支持旧版浏览器,你可能必须放弃新技术,或在所需之处把代码转换成过时语法。</li> + <li> 一些设备可能会制定一些限制来防止网站运行缓慢或显示效果糟糕。例如:若一个网站的设计效果在PC端很棒,在移动设备上就会显得很小,很不易于浏览。若你的网站要加载体积很大的动画,在高配设备上可能没问题,但在低配置的设备上就会迟钝又愚蠢。</li> +</ul> + +<p>此外还有很多原因。</p> + +<p>在后续文章中,我们会探究常见的跨浏览器问题,并寻求解决方案。</p> + +<h2 id="跨浏览器测试的工作流">跨浏览器测试的工作流</h2> + +<p>这一系列跨浏览器测试工作也行听起来耗时且可怕,但实际上不是——你只需仔细计划,并确保在恰当之处做足测试以防遭遇意外故障。假如你正在开发一个大型项目,你应该有规律地进行测试,确保新功能正确地服务你的目标用户,以及新增功能不会与已有功能冲突。</p> + +<p>如果你在项目中把所有测试工作留到最后,那么会比随时发现和解决Bug耗时更长,成本更高。</p> + +<p>一个项目的测试和排错工作流可以大致分为如下四个阶段(这只是粗略划分——因人而异):</p> + +<p><strong>初步规划>开发>测试/查错>修复/迭代</strong></p> + +<p>步骤2到步骤4在必要时应多次重复直到开发完成。我们会在后续章节详细探讨测试程序的不同之处,但现在我们只概述每个阶段可能出现的问题。</p> + +<h3 id="初步规划">初步规划</h3> + + + +<p>在初步规划阶段,你会和网站主人/客户(可能是你的老板或来自客户公司的人)开一些计划会议,你们会决定这个网站到底是什么——应该又什么内容和功能、什么样的外观等等。此时你也会想知道开发周期是多长——何时是他们的截止日期、他们会支付你多少酬劳。我们不会深入这些细节,但跨浏览器问题会大大影响这个计划。</p> + +<p>一旦你了解了需求,确定了技术选型,你就该开始调查目标用户——使用这个网站的用户用什么浏览器、什么设备等等。也行客户通过前期调查已经有了这方面的数据,比如通过他们的其他网站,或你将要开发的网站的之前版本。若没有,你将要通过搜寻其他资源,诸如竞品的使用数据,或网站将要服务的国家来了解清楚。也可以凭直觉。</p> + +<p>举个例子,你要开发一个服务北美客户的电子商务网站。该网站应该完美支持最流行的PC/移动端(iOS、Android、Windows phone)浏览器的最新几个版本——应包括Chrome(和相同渲染引擎的Opera)、Firefox、IE/Edge以及Safari。它还应为IE 8和9提供可接受的体验,以及遵循 WCAG AA指南。</p> + +<p>现在你已知道你的目标测试平台,你应回顾功能需求和技术选型。例如:如果这个电子商务站点的拥有者想要在页面上实现基于WebGl的3D产品展示,那他们必须放弃IE 11及以前的版本。你要承诺为低版本用户提供没有3D展示的版本。</p> + +<p>你应该列出这些潜在的问题。</p> + + + +<div class="note"> +<p><strong>注意:</strong>你可在MDN——就是本网站——找到浏览器对不同技术功能的支持情况的信息。</p> +</div> + +<p>一旦你们就这些问题达成共识,你就可以开始开发网站了。</p> + +<h3 id="开发">开发</h3> + +<p>现在到了开发阶段。你应该把开发分成不同模块,例如你可以分成首页、产品页、购物车、支付流程等。然后你可以再进行细分——实现公共的页头页脚,实现产品页细节视图,实现购物车组件等。</p> + +<p>有一些跨浏览器开发的普适策略,如:</p> + +<ul> + <li>让所有功能尽可能在所有目标浏览器上运行。这也许涉及到写不同的代码,让不同浏览器中的功能以不同方式实现,或使用一个<code>{{glossary("Polyfill")}}</code>模拟缺失的支撑,或者使用一个库,允许你写一点代码,然后根据浏览器支持的内容在后台执行不同的操作。</li> + <li>接受这个现实:一些东西在不同浏览器运行效果不同,在不支持完整功能的浏览器中提供不同的(可接受的)的解决方案。因为设备限制,有时这是不可避免的——不管你怎样设计网站,在影院宽屏上和在4寸屏上的视觉效果是不同的。</li> + <li> 接受这个现实:你的网站在旧版浏览器上不能用,那就忽略旧版。假设你的客户/用户认为可以就没事。</li> +</ul> + +<p>一般来说你的开发会涉及到一个上述三个方法的结合。最重要的是你在提交每个小部分之前都要测试——别把测试放到最后!</p> + +<h3 id="测试查错">测试/查错</h3> + +<p>在每个实现阶段之后,你需要测试这些新功能。要开始测试,你应该确保你的代码没有能够阻止功能运行的一般性错误:</p> + +<ol> + <li>在一些稳定浏览器中测试,例如Firefox、Safari、Chrome或IE/Edge。</li> + <li>做一些低可用性测试,比如尝试只用键盘使用网站,或通过屏幕阅读器访问,来检查可操纵性。</li> + <li>在移动端测试,例如iOS或Android。</li> +</ol> + +<p>此时,你要修复一切发现的问题。</p> + +<p>接下来,你应该试在所有浏览器上测试并集中精力排除跨浏览器问题(关于<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies#Gotta_test_%27em_all">determining your target browers</a>,请查阅下一篇文章获取更多信息)。例如</p> + +<ul> + <li>尝试在所有现代桌面浏览器上测试最新的修改——包括(理想条件下,Windows、Mac、Linux的)Firefox、Chrome、Opera、IE、Edge和Safari。</li> + <li>在常用的手机和平板浏览器中测试(例如iPhone/iPad上的iOS Safari、iPhone/iPad/Android的Chrome和Firefox)。</li> + <li>也要在你所列出的其他目标浏览器中测试。</li> +</ul> + +<p>最起码你要独自进行你能实现的所有测试(如果有团队,你可拉队友来帮你)。你应该尽可能在真实物理设备上进行测试。</p> + +<p>如果你无法测试所有那些不同的浏览器、操作系统和物理真机,你也可以用模拟器(在PC端用软件模拟设备)和虚拟机(能使你在PC上模拟多种操作系统/软件的软件)。这是很流行的选择,特别是在某些环境中——例如,Windows不允许你在同一台机器上模拟安装多个版本的Windows,那么使用虚拟机就是唯一选择。</p> + +<p>另一个选择是用户群组——使用开发团队之外的一组人测试你的网站。可以是一些朋友或家庭,其他员工们,一个班级或一个当地大学,或一个专业测试团队。</p> + +<p>最终,通过使用统计或自动化工具,你的测试会更智能;当你的项目变得更大,这是个明智的选择,毕竟手动做全部这些测试会花很长时间。你可以建立你的自动化测试系统(Selenium正在成为最流行选择),这样你就可以实现诸如在一些不同浏览器加载你的网站的功能,以及:</p> + +<ul> + <li>看一个按钮点击事件是否成功生效(例如显示地图),测试完成后就显示结果。</li> + <li>逐个截屏,允许你检查是否每个布局在跨浏览器时是否一致。</li> +</ul> + +<p>如果你愿意,你也可以更深入一些。如果你希望为测试投资一些钱,一些诸如<a href="https://saucelabs.com/">Sauce Labs</a>和<a href="https://www.browserstack.com/">Browser Stack</a>的商业工具都可以为你做这些事,你不必操心测试的构建。搭建一个自动为你运行的测试环境也是可能的,之后,你只要把测试通过的更改代码签入中央代码仓库就可以了。</p> + +<h4 id="在预发布的浏览器上测试">在预发布的浏览器上测试</h4> + +<p>测试即将发布的浏览器版本也是个不错的注意。请看下面的链接:</p> + +<ul> + <li><a href="https://www.mozilla.org/en-US/firefox/developer/">Firefox Developer Edition</a></li> + <li><a href="https://insider.windows.com/">Edge Insider Preview</a></li> + <li><a href="https://developer.apple.com/safari/technology-preview/">Safari Technology Preview</a></li> + <li><a href="https://www.google.com/chrome/browser/canary.html">Chrome Canary</a></li> + <li><a href="http://www.opera.com/computer/beta">Opera Developer</a></li> +</ul> + +<p>假如你使用了很新的技术并想测试最新版本的实现,或者你在最新版浏览器中遇到故障,你想检查浏览器开发者是否在新版本中修复了这个问题,这将是很流行的做法。</p> + +<h3 id="修复迭代">修复/迭代</h3> + +<p>当你发现一个Bug,你需要尝试修复它。</p> + +<p>首要之举是尽可能锁定Bug出现之处。从Bug报告者那里尽可能多的获得信息——什么平台、设备、浏览器版本等等。在相似环境(比如不同桌面平台的同一个浏览器版本,或同一平台中同一浏览器的小差别版本)中测试来检查该Bug波及多大范围。</p> + +<p>那可能不是你的错误——如果是一个浏览器的Bug,那就希望厂商尽快修复它。也许它已经被修复了——例如若一个Bug存在于Firefox版本49,但Firefox Nightly中已经不存在了。如果还未修复,你可能需要提交一个Bug报告。</p> + +<p>如果是你的错误,你需要修复它!查出导致该Bug的原因(再次,查阅<a href="/en-US/docs/Learn/HTML/Introduction_to_HTML/Debugging_HTML">Debugging HTML</a>,<a href="/en-US/docs/Learn/CSS/Introduction_to_CSS/Debugging_CSS">Debugging CSS</a>,和<a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">What went wrong? Troubleshooting JavaScript</a>)。当你找到原因,你需要决定如何在产生问题的浏览器中解决它——你不能直接改掉问题代码,因为这会在其他浏览器中导致问题。普遍做法是以某种方式分叉代码,例如用Javascript功能检测代码来检测问题功能不运行的情况,并运行一些在那些情况下生效的代码。</p> + +<p>Once a fix has been made, you'll want to repeat your testing process to make sure your fix is working OK, and hasn't caused the site to break in other places or in other browsers.</p> + +<p>一旦修复完成,你应该重复测试来确保你的修复工作有效,并没有导致网站的其他地方在其他浏览器中出问题。</p> + +<h2 id="报告Bug">报告Bug</h2> + +<p>重申上述内容:如果你发现浏览器Bug,你应该:</p> + +<ul> + <li><a href="https://bugzilla.mozilla.org/">Firefox Bugzilla</a></li> + <li><a href="https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/">EdgeHTML issue tracker</a></li> + <li><a href="https://bugs.webkit.org/">Safari</a></li> + <li><a href="https://bugs.chromium.org/p/chromium/issues/list">Chrome</a></li> + <li><a href="https://bugs.opera.com/wizard/desktop">Opera</a></li> +</ul> + +<h2 id="总结">总结</h2> + +<p>本文应该能够让你对跨浏览器测试最重要的部分有一个更高层次的理解。有了这些知识的武装,你现在可以继续并开始学习跨浏览器测试策略了。</p> + +<p>{{NextMenu("Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="指南">指南</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">测试策略</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">处理常见的HTML和CSS问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的可访问性问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">实现特征检查</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">建立你自己的自动化测试环境</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/javascript/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/javascript/index.html new file mode 100644 index 0000000000..68c0b88842 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/javascript/index.html @@ -0,0 +1,509 @@ +--- +title: 处理常见的 JavaScript问题 +slug: Learn/Tools_and_testing/Cross_browser_testing/JavaScript +translation_of: Learn/Tools_and_testing/Cross_browser_testing/JavaScript +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS","Learn/Tools_and_testing/Cross_browser_testing/Accessibility", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">现在我们看看如何跟踪一些常见的浏览器JavaScript问题并且如何修复它们。这个包括如何使用浏览器开发工具跟踪和修复问题、使用Polyfill或第三方库解决问题、如果让一些现代JavaScript的特性也能在旧的浏览器下面工作等。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟练使用 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言; 以及一些<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试的高级概念</a>.</td> + </tr> + <tr> + <th scope="row">本文目标:</th> + <td>可以分析一些常见的JavaScript跨浏览器问题,并且学会使用一些工具和技术修复它们</td> + </tr> + </tbody> +</table> + +<h2 id="The_trouble_with_JavaScript">The trouble with JavaScript</h2> + +<p>Historically, JavaScript was plagued with cross-browser compatibility problems — back in the 1990s, the main browser choices back then (Internet Explorer and Netscape) had scripting implemented in different language flavours (Netscape had JavaScript, IE had JScript and also offered VBScript as an option), and while at least JavaScript and JScript were compatible to some degree (both based on the {{glossary("ECMAScript")}} specification), things were often implemented in conflicting, incompatible ways, causing developers many nightmares.</p> + +<p class="entry-title">Such incompatibility problems persisted well into the early 2000s, as old browsers were still being used and still needed supporting. This is one of the main reasons why libraries like <a href="http://jquery.com/">jQuery</a> came into existence — to abstract away differences in browser implementations (e.g. see the code snippet in <a href="/en-US/docs/AJAX/Getting_Started#Step_1_%E2%80%93_How_to_make_an_HTTP_request">How to make an HTTP request</a>) so developers only have to write one simple bit of code (see <code><a href="http://api.jquery.com/jquery.ajax/">jQuery.ajax()</a></code>). jQuery (or whatever library you are using) will then handle the differences in the background, so you don't have to.</p> + +<p>Things have got much better since then; modern browsers do a good job of supporting "classic JavaScript features", and the requirement to use such code has diminished as the requirement to support older browsers has lessened (although bear in mind that they have not gone away altogether).</p> + +<p>These days, most cross-browser JavaScript problems are seen:</p> + +<ul> + <li>When bad quality browser sniffing code, feature detection code, and vendor prefix usage blocks browsers from running code they could otherwise use just fine.</li> + <li>When developers make use of new/nascent JavaScript features (for example <a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla">ECMAScript 6</a> / <a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla">ECMAScript Next</a> features, modern Web APIs...) in their code, and find that such features don't work in older browsers.</li> +</ul> + +<p>We'll explore all these problems and more below.</p> + +<h2 id="修复一般的JavaScript问题">修复一般的JavaScript问题</h2> + +<p>As we said in the <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#First_things_first_fixing_general_problems">previous article</a> on HTML/CSS, you should make sure your code is working generally, before going on to concentrate on the cross-browser issues. If you are not already familiar with the basics of <a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">Troubleshooting JavaScript</a>, you should study that article before moving on. There are a number of common JavaScript problems that you will want to be mindful of, such as:</p> + +<ul> + <li>Basic syntax and logic problems (again, check out <a href="/en-US/docs/Learn/JavaScript/First_steps/What_went_wrong">Troubleshooting JavaScript</a>).</li> + <li>Making sure variables, etc. are defined in the correct scope, and you are not running into conflicts between items declared in different places (see <a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Function_scope_and_conflicts">Function scope and conflicts</a>).</li> + <li>Confusion about <a href="/en-US/docs/Web/JavaScript/Reference/Operators/this">this</a>, in terms of what scope it applies to, and therefore if its value is what you intended. You can read <a href="/en-US/docs/Learn/JavaScript/Objects/Basics#What_is_this">What is "this"?</a> for a light introduction; you should also study examples like <a href="https://github.com/mdn/learning-area/blob/7ed039d17e820c93cafaff541aa65d874dde8323/javascript/oojs/assessment/main.js#L143">this one</a>, which shows a typical pattern of saving a <code>this</code> scope to a separate variable, then using that variable in nested functions so you can be sure you are applying functionality to the correct <code>this</code> scope.</li> + <li>Incorrectly using functions inside loops — for example, in <a href="https://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/bad-for-loop.html">bad-for-loop.html</a> (see <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/bad-for-loop.html">source code</a>), we loop through 10 iterations, each time creating a paragraph and adding an <a href="/en-US/docs/Web/API/GlobalEventHandlers/onclick">onclick</a> event handler to it. When clicked, each one should alert a message containing its number (the value of <code>i</code> at the time it was created), however each one reports <code>i</code> as 11, because for loops do all their iterating before nested functions are invoked. If you want this to work correctly, you need to define a function to add the handler separately, calling it on each iteration and passing it the current value of <code>para</code> and <code>i</code> each time (or something similar). See <a href="https://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/good-for-loop.html">good-for-loop.html</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/good-for-loop.html">source code</a> also) for a version that works.</li> + <li>Making sure asynchronous operations have returned before trying to use the values they return. For example, <a href="/en-US/docs/AJAX/Getting_Started#Step_3_%E2%80%93_A_Simple_Example">this Ajax example</a> checks to make sure the request is complete and the response has been returned before trying to use the response for anything. This kind of operation has been made easier to handle by the introduction to <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> to the JavaScript language.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://www.toptal.com/javascript/10-most-common-javascript-mistakes">Buggy JavaScript Code: The 10 Most Common Mistakes JavaScript Developers Make</a> has some nice discussions of these common mistakes and more.</p> +</div> + +<h3 id="Linters">Linters</h3> + +<p>As with <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Linters">HTML and CSS</a>, you can ensure better quality, less error-prone JavaScript code using a linter, which points out errors and can also flag up warnings about bad practices, etc., and be customized to be stricter or more relaxed in their error/warning reporting. The JavaScript/ECMAScript linters we'd recommend are <a href="http://jshint.com/">JSHint</a> and <a href="http://eslint.org/">ESLint</a>; these can be used in a variety of ways, some of which we'll detail below.</p> + +<h4 id="Online">Online</h4> + +<p>The <a href="http://jshint.com/">JSHint homepage</a> provides an online linter, which allows you to enter your JavaScript code on the left and provides an output on the right, including metrics, warnings, and errors.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14175/jshint-online.png" style="display: block; margin: 0 auto;"></p> + +<h4 id="Code_editor_plugins">Code editor plugins</h4> + +<p>It is not very convenient to have to copy and paste your code over to a web page to check its validity several times. What you really want is a linter that will fit into your standard workflow with the minimum of hassle. Many code editors have linter plugins, for example Github's <a href="https://atom.io/">Atom</a> code editor has a JSHint plugin available.</p> + +<p>To install it:</p> + +<ol> + <li>Install Atom (if you haven't got an up-to-date version already installed) — download it from the Atom page linked above.</li> + <li>Go to Atom's <em>Preferences...</em> dialog (e.g. by Choosing <em>Atom > Preferences...</em> on Mac, or <em>File > Preferences...</em> on Windows/Linux) and choose the <em>Install</em> option in the left-hand menu.</li> + <li>In the <em>Search packages</em> text field, type "jslint" and press Enter/Return to search for linting-related packages.</li> + <li>You should see a package called <strong>lint</strong> at the top of the list. Install this first (using the <em>Install</em> button), as other linters rely on it to work. After that, install the <strong>linter-jshint</strong> plugin.</li> + <li>After the packages have finished installing, try loading up a JavaScript file: you'll see any issues highlighted with green (for warnings) and red (for errors) circles next to the line numbers, and a separate panel at the bottom provides line numbers, error messages, and sometimes suggested values or other fixes.</li> +</ol> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14173/jshint-linter.png" style="display: block; margin: 0 auto;">Other popular editors have similar linting packages available. For example, see the "Plugins for text editors and IDEs" section of the <a href="http://jshint.com/install/">JSHint install page</a>.</p> + +<h4 id="其他方式">其他方式</h4> + +<p>There are other ways to use such linters; you can read about them on the <a href="http://jshint.com/install/">JSHint</a> and <a href="http://eslint.org/docs/user-guide/getting-started">ESLint</a> install pages.</p> + +<p>It is worth mentioning command line uses — you can install these tools as command line utilities (available via the CLI — command line interface) using npm (Node Package Manager — you'll have to install <a href="https://nodejs.org/en/">NodeJS</a> first). For example, the following command installs JSHint:</p> + +<pre class="brush: bash">npm install -g jshint +</pre> + +<p>You can then point these tools at JavaScript files you want to lint, for example:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14171/js-hint-commandline.png" style="display: block; height: 478px; margin: 0px auto; width: 697px;">You can also use these tools with a task runner/build tool such as <a href="http://gulpjs.com/">Gulp</a> or <a href="https://webpack.github.io/">Webpack</a> to automatically lint your JavaScript during development. (see <a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing#Using_a_task_runner_to_automate_testing_tools">Using a task runner to automate testing tools</a> in a later article.) See <a href="http://eslint.org/docs/user-guide/integrations">ESLint integrations</a> for ESLint options; JSHint is supported out of the box by Grunt, and also has other integrations available, e.g. <a href="https://github.com/webpack/jshint-loader">JSHint loader for Webpack</a>.</p> + +<div class="note"> +<p><strong>Note</strong>: ESLint takes a bit more setup and configuration than JSHint, but it is more powerful too.</p> +</div> + +<h3 id="浏览器开发者工具">浏览器开发者工具</h3> + +<p>浏览器开发者工具有很多功能可以帮助定位JavaScript的问题,尤其是在开发的工程中,JavaScript的Console会提醒一些错误信息</p> + +<p>下载示例文件 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/broken-ajax.html">broken-ajax.html</a> 到本地 (也可以阅读 <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/broken-ajax.html">source code</a> ). 如果你打开console面板,你将会看到下图中的输出信息:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14165/broken-ajax.png" style="display: block; height: 86px; margin: 0px auto; width: 635px;"></p> + +<p>错误提示 "TypeError: jsonObj is null", 标示出问题出现在代码的第37行. 如果你看了源代码, 相关的代码如下面所示:</p> + +<pre class="brush: js">function populateHeader(jsonObj) { + var myH1 = document.createElement('h1'); +<strong> myH1.textContent = jsonObj['squadName'];</strong> + header.appendChild(myH1); + + ...</pre> + +<p>So the code falls over as soon as we try to access <code>jsonObj</code> (which as you might expect, is supposed to be a <a href="/en-US/docs/Learn/JavaScript/Objects/JSON">JSON object</a>). This is supposed to be fetched from an external <code>.json</code> file using the following XMLHttpRequest call:</p> + +<pre class="brush: js">var requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json'; +var request = new XMLHttpRequest(); +request.open('GET', requestURL); +request.send(); + +<strong>var superHeroes = request.response;</strong> +populateHeader(superHeroes); +showHeroes(superHeroes);</pre> + +<p>But this fails.</p> + +<h4 id="Console相关API">Console相关API</h4> + +<p>You may already know what is wrong with this code, but let's explore it some more to show how you could investigate this. For a start, there is a <a href="/en-US/docs/Web/API/Console">Console</a> API that allows JavaScript code to interact with the browser's JavaScript console. It has a number of features available, but the main one you'll use often is <code><a href="/en-US/docs/Web/API/Console/log">console.log()</a></code>, which prints a custom message to the console.</p> + +<p>Try inserting the following line just below line 31 (bolded above):</p> + +<pre class="brush: js">console.log('Response value: ' + superHeroes);</pre> + +<p>Refresh the page in the browser, and you will get an output in the console like so:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14167/console-log.png" style="display: block; height: 99px; margin: 0px auto; width: 638px;"></p> + +<p>The <code>console.log()</code> output shows that the <code>superHeroes</code> object doesn't appear to contain anything, although note that the error message has now changed, to "TypeError: heroes is undefined". This shows that the error is intermittent, giving further evidence that this is some kind of asynchronous error. Let's fix the current error and move on — remove the <code>console.log()</code> line, and update this code block:</p> + +<pre class="brush: js">var superHeroes = request.response; +populateHeader(superHeroes); +showHeroes(superHeroes);</pre> + +<p>to the following:</p> + +<pre class="brush: js">request.onload = function() { + var superHeroes = request.response; + populateHeader(superHeroes); + showHeroes(superHeroes); +}</pre> + +<p>This solves the asynchronous issue, by ensuring that the functions are not run and passed the <code>superHeroes</code> object until the response has finished loading and is available.</p> + +<p>So to summarize, anytime something is not working and a value does not appear to be what it is meant to be at some point in your code, you can use <code>console.log()</code> to print it out and see what is happening.</p> + +<h4 id="Using_the_JavaScript_debugger">Using the JavaScript debugger</h4> + +<p>We have solved one problem, but we are still stuck with the error message "TypeError: heroes is undefined", reported on line 51. Let's investigate this now, using a more sophisticated feature of browser developer tools: the <a href="/en-US/docs/Tools/Debugger">JavaScript debugger</a> as it is called in Firefox.</p> + +<div class="note"> +<p><strong>Note</strong>: Similar tools are available in other browsers; the <a href="https://developers.google.com/web/tools/chrome-devtools/#sources">Sources tab</a> in Chrome, Debugger in Safari (see <a href="https://developer.apple.com/safari/tools/">Safari Web Development Tools</a>), etc.</p> +</div> + +<p>In Firefox, the Debugger tab looks as follows:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14169/debugger-tab.png" style="display: block; height: 253px; margin: 0px auto; width: 800px;"></p> + +<ul> + <li>On the left, you can select the script you want to debug (in this case we have only one).</li> + <li>The center panel shows the code in the selected script.</li> + <li>The right-hand panel shows useful details pertaining to the current environment — <em>Breakpoints</em>, <em>Callstack</em> and currently active <em>Scopes</em>.</li> +</ul> + +<p>The main feature of such tools is the ability to add breakpoints to code — these are points where the execution of the code stops, and at that point you can examine the environment in its current state and see what is going on.</p> + +<p>Let's get to work. First of all, we know that the error is being thrown at line 51. Click on line number 51 in the center panel to add a breakpoint to it (you'll see a blue arrow appear over the top of it). Now refresh the page (Cmd/Ctrl + R) — the browser will pause execution of the code at line 51. At this point, the right-hand side will update to show some very useful information.</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14163/breakpoint.png" style="display: block; margin: 0 auto;"></p> + +<ul> + <li>Under <em>Breakpoints</em>, you'll see the details of the break-point you have set.</li> + <li>Under <em>Call Stack</em>, you'll see two entries — this is basically a list of the series of functions that were invoked to cause the current function to be invoked. At the top, we have <code>showHeroes()</code> the function we are currently in, and below we have <code>request.onload</code>, which stores the event handler function containing the call to <code>showHeroes()</code>.</li> + <li>Under <em>Scopes</em>, you'll see the currently active scope for the function we are looking at. We only have two — <code>showHeroes</code>, and <code>Window</code> (the global scope). Each scope can be expanded to show the values of variables inside the scope at the point that execution of the code was stopped.</li> +</ul> + +<p>We can find out some very useful information in here.</p> + +<ol> + <li>Expand the <code>showHeroes</code> scope — you can see from this that the heroes variable is undefined, indicating that accessing the <code>members</code> property of <code>jsonObj</code> (first line of the function) didn't work.</li> + <li>You can also see that the <code>jsonObj</code> variable is storing a text string, not a JSON object.</li> + <li>Exploring further down the call stack, click <code>request.onload</code> in the <em>Call Stack</em> section. The view will update to show the <code>request.onload</code> function in the center panel, and its scopes in the <em>Scopes</em> section.</li> + <li>Now if you expand the <code>request.onload</code> scope, you'll see that the <code>superHeroes</code> variable is a text string too, not an object. This settles it — our <code><a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a></code> call is returning the JSON as text, not JSON.</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: We'd like you to try fixing this problem yourself. To give you a clue, you can either <a href="/en-US/docs/Web/API/XMLHttpRequest/responseType">tell the XMLHttpRequest object explicitly to return JSON format</a>, or <a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON#Converting_between_objects_and_text">convert the returned text to JSON</a> after the response arrives. If you get stuck, consult our <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fixed-ajax.html">fixed-ajax.html</a> example.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: The debugger tab has many other useful features that we've not discussed here, for example conditional breakpoints and watch expressions. For a lot more information, see the <a href="/en-US/docs/Tools/Debugger">Debugger</a> page.</p> +</div> + +<h3 id="Performance_issues">Performance issues</h3> + +<p>As your apps get more complex and you start to use more JavaScript, you may start to run into performance problems, especially when viewing apps on slower devices. Performance is a big topic, and we don't have time to cover it in detail here. Some quick tips are as follows:</p> + +<ul> + <li>To avoid loading more JavaScript than you need, bundle your scripts into a single file using a solution like <a href="http://browserify.org/">Browserify</a>. In general, reducing the number of HTTP requests is very good for performance.</li> + <li>Make your files even smaller by minifying them before you load them onto your production server. Minifying squashes all the code together onto a huge single line, making it take up far less file size. It is ugly, but you don't need to read it when it is finished! This is best done using a minification tool like <a href="https://github.com/mishoo/UglifyJS2">Uglify</a> (there's also an online version — see <a href="https://jscompress.com/">JSCompress.com</a>)</li> + <li>When using APIs, make sure you turn off the API features when they are not being used; some API calls can be really expensive on processing power. For example, when showing a video stream, make sure it is turned off when you can't see it. When tracking a device's location using repeated Geolocation calls, make sure you turn it off when the user stops using it.</li> + <li>Animations can be really costly for performance. A lot of JavaScript libraries provide animation capabilities programmed by JavaScript, but it is much more cost effective to do the animations via native browser features like <a href="/en-US/docs/Web/CSS/CSS_Animations">CSS Animations</a> (or the nascent <a href="/en-US/docs/Web/API/Web_Animations_API">Web Animations API</a>) than JavaScript. Read Brian Birtles' <a href="https://hacks.mozilla.org/2016/08/animating-like-you-just-dont-care-with-element-animate/">Animating like you just don’t care with Element.animate</a> for some really useful theory on why animation is expensive, tips on how to improve animation performance, and information on the Web Animations API.</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: Addy Osmani's <a href="https://www.smashingmagazine.com/2012/11/writing-fast-memory-efficient-javascript/" rel="bookmark" title="Read 'Writing Fast, Memory-Efficient JavaScript'"><span class="headline">Writing Fast, Memory-Efficient JavaScript</span></a> <span class="headline">contains a lot of detail and some excellent tips for boosting JavaScript performance.</span></p> +</div> + +<h2 id="Cross-browser_JavaScript_problems">Cross-browser JavaScript problems</h2> + +<p>In this section, we'll look at some of the more common cross-browser JavaScript problems. We'll break this down into:</p> + +<ul> + <li>Using modern core JavaScript features</li> + <li>Using modern Web API features</li> + <li>Using bad browser sniffing code</li> + <li>Performance problems</li> +</ul> + +<h3 id="Using_modern_JavaScriptAPI_features">Using modern JavaScript/API features</h3> + +<p>In the <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Older_browsers_not_supporting_modern_features">previous article</a> we described some of the ways in which HTML and CSS errors and unrecognized features can be handled due to the nature of the languages. JavaScript is not as permissive as HTML and CSS however — if the JavaScript engine encounters mistakes or unrecognized syntax, more often than not it will throw errors.</p> + +<p>There are a number of modern JavaScript language features defined in recent versions of the specs (<a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla">ECMAScript 6</a> / <a href="/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_Next_support_in_Mozilla">ECMAScript Next</a>) that simply won't work in older browsers. Some of these are syntactic sugar (basically an easier, nicer way of writing what you can already do using existing features), and some offer interesting new possibilities.</p> + +<p>For example:</p> + +<ul> + <li><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a> are a great new feature for performing asynchronous operations and making sure those operations are complete before code that relies on their results is used for something else. As an example, the <a href="https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch">Fetch API</a> (a modern equivalent to <a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHTTPRequest</a>) uses promises to fetch resources across the network and make sure that the response has been returned before they are used (for example, displaying an image inside an {{htmlelement("img")}} element). They are not supported in IE at all but are supported across all modern browsers.</li> + <li>Arrow functions provide a shorter, more convenient syntax for writing <a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Anonymous_functions">anonymous functions</a>, which also has other advantages (see <a href="/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions">Arrow functions</a>). For a quick example, see <a href="https://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/arrow-function.html">arrow-function.html</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/arrow-function.html">source code</a> also). Arrow functions are supported across all modern browsers, except for IE and Safari.</li> + <li>Declaring <a href="/en-US/docs/Web/JavaScript/Reference/Strict_mode">strict mode</a> at the top of your JavaScript code causes it to be parsed with a stricter set of rules, meaning that more warnings and errors will be thrown, and some things will be disallowed that would otherwise be acceptable. It is arguably a good idea to use strict mode, as it makes for better, more efficient code, however it has limited/patchy support across browsers (see <a href="/en-US/docs/Web/JavaScript/Reference/Strict_mode#Strict_mode_in_browsers">Strict mode in browsers</a>).</li> + <li><a href="/en-US/docs/Web/JavaScript/Typed_arrays">Typed arrays</a> allow JavaScript code to access and manipulate raw binary data, which is necessary as browser APIs for example start to manipulate streams of raw video and audio data. These are available in IE10 and above, and all modern browsers.</li> +</ul> + +<p>There are also many new APIs appearing in recent browsers, which don't work in older browsers, for example:</p> + +<ul> + <li><a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB API</a>, <a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage API</a>, and others for storing website data on the client-side.</li> + <li><a href="/en-US/docs/Web/API/Web_Workers_API">Web Workers API</a> for running JavaScript in a separate thread, helping to improve performance.</li> + <li><a href="/en-US/docs/Learn/WebGL">WebGL API</a> for real 3D graphics.</li> + <li><a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> for advanced audio manipulation.</li> + <li><a href="/en-US/docs/Web/API/WebRTC_API">WebRTC API</a> for multi-person, real-time video/audio connectivity (e.g. video conferencing).</li> + <li><a href="/en-US/docs/Web/API/WebVR_API">WebVR API</a> for engineering virtual reality experiences in the browser (e.g. controlling a 3D view with data input from VR Hardware)</li> +</ul> + +<p>There are a few strategies for handling incompatibilities between browsers relating to feature support; let's explore the most common ones.</p> + +<div class="note"> +<p><strong>Note</strong>: These strategies do not exist in separate silos — you can, of course combine them as needed. For example, you could use feature detection to determine whether a feature is supported; if it isn't, you could then run code to load a polyfill or a library to handle the lack of support.</p> +</div> + +<h4 id="Feature_detection">Feature detection</h4> + +<p>The idea behind feature detection is that you can run a test to determine whether a JavaScript feature is supported in the current browser, and then conditionally run code to provide an acceptable experience both in browsers that do and don't support the feature. As a quick example, the <a href="/en-US/docs/Web/API/Geolocation/Using_geolocation">Geolocation API</a> (which exposes available location data for the device the web browser is running on) has a main entry point for its use — a <code>geolocation</code> property available on the global <a href="/en-US/docs/Web/API/Navigator">Navigator</a> object. Therefore, you can detect whether the browser supports geolocation or not by using something like the following:</p> + +<pre class="language-js"><span class="keyword token">if</span><span class="punctuation token">(</span><span class="string token">"geolocation"</span> <span class="keyword token">in</span> navigator<span class="punctuation token">)</span> <span class="punctuation token">{</span> + navigator<span class="punctuation token">.</span>geolocation<span class="punctuation token">.</span><span class="function token">getCurrentPosition</span><span class="punctuation token">(</span><span class="keyword token">function</span><span class="punctuation token">(</span>position<span class="punctuation token">)</span> <span class="punctuation token">{</span> + // show the location on a map, perhaps using the Google Maps API + <span class="punctuation token">}</span><span class="punctuation token">)</span><span class="punctuation token">; +}</span> <span class="keyword token">else</span> <span class="punctuation token">{</span> + // Give the user a choice of static maps instead perhaps +<span class="punctuation token">}</span></pre> + +<p>You could also write such a test for a CSS feature, for example by testing for the existence of <em><a href="/en-US/docs/Web/API/HTMLElement/style">element.style.property</a></em> (e.g. <code>paragraph.style.transform !== undefined</code>). But for both CSS and JavaScript, it is probably better to use an established feature detection library rather than writing your own all the time. Modernizr is the industry standard for feature detection tests.</p> + +<p>As a last point, don't confuse feature detection with <strong>browser sniffing</strong> (detecting what specific browser is accessing the site) — this is a terrible practice that should be discouraged at all costs. See {{anch("Using bad browser sniffing code")}}, later on, for more details.</p> + +<div class="note"> +<p><strong>Note</strong>: Some features are known to be undetectable — see Modernizr's list of <a href="https://github.com/Modernizr/Modernizr/wiki/Undetectables">Undetectables</a>.</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: Feature detection will be covered in a lot more detail in its own dedicated article, later in the module.</p> +</div> + +<h4 id="Libraries">Libraries</h4> + +<p>JavaScript libraries are essentially third party units of code that you can attach to your page, providing you with a wealth of ready-made functionality that can be used straight away, saving you a lot of time in the process. A lot of JavaScript libraries probably came into existence because their developer was writing a set of common utility functions to save them time when writing future projects, and decided to release them into the wild because other people might find them useful too.</p> + +<p>JavaScript libraries tend to come in a few main varieties (some libraries will serve more than one of these purposes):</p> + +<ul> + <li>Utility libraries: Provide a bunch of functions to make mundane tasks easier and less boring to manage. <a href="http://jquery.com/">jQuery</a> for example provides its own fully-featured selectors and DOM manipuation libraries, to allow CSS-selector type selecting of elements in JavaScript and easier DOM building. It is not so important now we have modern features like {{domxref("Document.querySelector()")}}/{{domxref("Document.querySelectorAll()")}}/{{domxref("Node")}} methods available across browsers, but it can still be useful when older browsers need supporting.</li> + <li>Convenience libraries: Make difficult things easier to do. For example, the <a href="/en-US/docs/Learn/WebGL">WebGL API</a> is really complex and challenging to use when you write it directly, so the <a href="https://threejs.org/">Three.js</a> library (and others) is built on top of WebGL and provides a much easier API for creating common 3D objects, lighting, textures, etc. The <a href="/en-US/docs/Web/API/Service_Worker_API">Service Worker API</a> is also very complex to use, so code libraries have started appearing to make common Service Worker uses-cases much easier to implement (see the <a href="https://serviceworke.rs/">Service Worker Cookbook</a> for several useful code samples).</li> + <li>Effects libraries: These libraries are designed to allow you to easily add special effects to your websites. This was more useful back when {{glossary("DHTML")}} was a popular buzzword, and implementing effect involved a lot of complex JavaScript, but these days browsers have a lot of built in CSS3 features and APIs to implementing effects more easily. See <a href="https://www.javascripting.com/animation/">JavaScripting.com/animation</a> for a list of libraries.</li> + <li>UI libraries: Provide methods for implementing complex UI features that would otherwise be challenging to implement and get working cross browser, for example <a href="http://jqueryui.com/">jQuery UI</a> and <a href="http://foundation.zurb.com/">Foundation</a>. These tend to be used as the basis of an entire site layout; it is often difficult to drop them in just for one UI feature.</li> + <li>Normalization libraries: Give you a simple syntax that allows you to easily complete a task without having to worry about cross browser differences. The library will manipulate appropriate APIs in the background so the functionality will work whatever the browser (in theory). For example, <a href="https://github.com/localForage/localForage">LocalForage</a> is a library for client-side data storage, which provides a simple syntax for storing and retrieving data. In the background, it uses the best API the browser has available for storing the data, whether that is <a href="/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a>, <a href="/en-US/docs/Web/API/Web_Storage_API">Web Storage</a>, or even WebSQL (which is now deprecated, but is still supported in some older versions of Safari/IE). As another example, jQuery</li> +</ul> + +<p>When choosing a library to use, make sure that it works across the set of browsers you want to support, and test your implementation thoroughly. Also make sure that the library is popular and well-supported, and isn't likely to just become obsolete next week. Talk to other developers to find out what they recommend, see how much activity and how many contributors the library has on Github (or wherever else it is stored), etc.</p> + +<div class="note"> +<p><strong>Note</strong>: <a href="https://www.javascripting.com/">JavaScripting.com</a> gives you a good idea of just how many JavaScript libraries there are available, and can be useful for finding libraries for specific purposes.</p> +</div> + +<p>Library usage at a basic level tends to consist of downloading the library's files (JavaScript, possibly some CSS or other dependencies too) and attaching them to your page (e.g. via a {{htmlelement("script")}} element), although there are normally many other usage options for such libraries, like installing them as <a href="https://bower.io/">Bower</a> components, or including them as dependencies via the <a href="https://webpack.github.io/">Webpack</a> module bundler. You will have to read the libraries' individual install pages for more information.</p> + +<div class="note"> +<p><strong>Note</strong>: You will also come across JavaScript frameworks in your travels around the Web, like <a href="http://emberjs.com/">Ember</a> and <a href="https://angularjs.org/">Angular</a>. Whereas libraries are often usable for solving individual problems and dropping into existing sites, frameworks tend to be more along the lines of complete solutions for developing complex web applications.</p> +</div> + +<h4 id="Polyfills">Polyfills</h4> + +<p>Polyfills also consist of 3rd party JavaScript files that you can drop into your project, but they differ from libraries — whereas libraries tend to enhance existing functionality and make things easier, polyfills provide functionality that doesn't exist at all. Polyfills use JavaScript or other technologies entirely to build in support for a feature that a browser doesn't support natively. For example, you might use a polyfill like <a href="https://github.com/stefanpenner/es6-promise">es6-promise</a> to make promises work in browsers where they are not supported natively.</p> + +<p class="gh-header-title instapaper_title">Modernizr's list of <a href="https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-Browser-Polyfills">HTML5 Cross Browser Polyfills</a> is a useful place to find polyfills for different purposes. Again, you should research them before you use them — make sure they work and are maintained.</p> + +<p class="gh-header-title instapaper_title">Let's work through an exercise — in this example we will use a Fetch polyfill to provide support for the Fetch API in older browsers; however we also need to use the es6-promise polyfill, as Fetch makes heavy use of promises, and browsers that don't support them will still be in trouble.</p> + +<ol> + <li class="gh-header-title instapaper_title">To get started, make a local copy of our <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fetch-polyfill.html">fetch-polyfill.html</a> example and <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/flowers.jpg">our nice image of some flowers</a> in a new directory. We are going to write code to fetch the flowers image and display it in the page.</li> + <li class="gh-header-title instapaper_title">Next, save copies of the <a href="https://raw.githubusercontent.com/github/fetch/master/fetch.js">Fetch polyfill</a> and the <a href="https://raw.githubusercontent.com/stefanpenner/es6-promise/master/dist/es6-promise.js">es6-promises polyfill</a> in the same directory as the HTML.</li> + <li class="gh-header-title instapaper_title">Apply the polyfill scripts to the page using the following code — place these above the existing {{htmlelement("script")}} element so they will be available on the page already when we start trying to use Fetch: + <pre class="brush: js"><script src="es6-promise.js"></script> +<script src="fetch.js"></script></pre> + </li> + <li>Inside the original {{htmlelement("script")}}, add the following code:</li> + <li> + <pre class="brush: js">var myImage = document.querySelector('.my-image'); + +fetch('flowers.jpg').then(function(response) { + response.blob().then(function(myBlob) { + var objectURL = URL.createObjectURL(myBlob); + myImage.src = objectURL; + }); +});</pre> + </li> + <li>Now if you load it in a browser that doesn't support Fetch (Safari and IE are obvious candidates), you should still see the flower image appear — cool! <br> + <img alt="" src="https://mdn.mozillademos.org/files/14183/fetch-image.jpg" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: You can find our finished version at <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/fetch-polyfill-finished.html">fetch-polyfill-finished.html</a> (see also the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fetch-polyfill-finished.html">source code</a>).</p> +</div> + +<div class="note"> +<p class="gh-header-title instapaper_title"><strong>Note</strong>: Again, there are many different ways to make use of the different polyfills you will encounter — consult each polyfill's individual documentation.</p> +</div> + +<p>One thing you might be thinking is "why should we always load the polyfill code, even if we don't need it?" This is a good point — as your sites get more complex and you start to use more libraries, polyfills, etc., you can start to load a lot of extra code, which can start to affect performance, especially on less-powerful devices. It makes sense to only load files as needed.</p> + +<p>Doing this requires some extra setup in your JavaScript. You need some kind of a feature detection test that detects whether the browser supports the feature we are trying to use:</p> + +<pre class="brush: js"><code class="language-js"><span class="hljs-keyword">if</span> (browserSupportsAllFeatures()) { + main(); +} <span class="hljs-keyword">else</span> { + loadScript(<span class="hljs-string">'polyfills.js'</span>, main); +} + +<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(err) </span>{ + <span class="hljs-comment">// actual app code goes in here</span> +}</code></pre> + +<p>So first we run a conditional that checks whether the function <code>browserSupportsAllFeatures()</code> returns true. If it does, we run the <code>main()</code> function, which will contain all our app's code. <code>browserSupportsAllFeatures()</code> looks like this:</p> + +<pre class="brush: js"><code class="language-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">browserSupportsAllFeatures</span>() </span>{ + <span class="hljs-keyword">return</span> <span class="hljs-built_in">window</span>.Promise && <span class="hljs-built_in">window</span>.fetch; +}</code></pre> + +<p>Here we are testing whether the <code><a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a></code> object and <code><a href="/en-US/docs/Web/API/GlobalFetch/fetch">fetch()</a></code> function exist in the browser. If both do, the function returns true. If the function returns <code>false</code>, then we run the code inside the second part of the conditional — this runs a function called loadScript(), which loads the polyfills into the page, then runs <code>main()</code> after the loading has finished. <code>loadScript()</code> looks like this:</p> + +<pre class="brush: js"><code class="language-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">loadScript</span>(<span class="hljs-params">src, done</span>) </span>{ + <span class="hljs-keyword">var</span> js = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'script'</span>); + js.src = src; + js.onload = <span class="hljs-function"><span class="hljs-keyword">function</span>() </span>{ + done(); + }; + js.onerror = <span class="hljs-function"><span class="hljs-keyword">function</span>() </span>{ + done(<span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'Failed to load script '</span> + src)); + }; + <span class="hljs-built_in">document</span>.head.appendChild(js); +}</code> +</pre> + +<p>This function creates a new <code><script></code> element, then sets its <code>src</code> attribute to the path we specified as the first argument (<code>'polyfills.js'</code> when we called it in the code above). When it has loaded, we run the function we specified as the second argument (<code>main()</code>). If an error occurs in the loading of the script, we still call the function, but with a custom error that we can retrieve to help debug a problem if it occurs.</p> + +<p>Note that polyfills.js is basically the two polyfills we are using put together into one file. We did this manually, but there are cleverer solutions that will automatically generate bundles for you — see <a href="http://browserify.org/">Browserify</a> (see <a href="https://www.sitepoint.com/getting-started-browserify/">Getting started with Browserify</a> for a basic tutorial). It is a good idea to bundle JS files into one like this — reducing the number of HTTP requests you need to make improves the performance of your site.</p> + +<p>You can see this code in action in <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/fetch-polyfill-only-when-needed.html">fetch-polyfill-only-when-needed.html</a> (see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/fetch-polyfill-only-when-needed.html">source code also</a>). We'd like to make it clear that we can't take credit for this code — it was originally written by Philip Walton. Check out his article <a href="https://philipwalton.com/articles/loading-polyfills-only-when-needed/">Loading Polyfills Only When Needed</a> for the original code, plus a lot of useful explanation around the wider subject).</p> + +<div class="note"> +<p><strong>Note</strong>: There are some 3rd party options to consider, for example <a href="https://polyfill.io/v2/docs/">Polyfill.io</a> — this is a meta-polyfill library that will look at each browser's capabilities and apply polyfills as needed, depending on what APIs and JS features you are using in your code.</p> +</div> + +<h4 id="JavaScript_transpiling">JavaScript transpiling</h4> + +<p>Another option that is becoming popular for people that want to use modern JavaScript features now is converting code that makes use of ECMAScript 6/ECMAScript 2015 features to a version that will work in older browsers.</p> + +<div class="note"> +<p><strong>Note</strong>: This is called "transpiling" — you are not compiling code into a lower level for to be run on a computer (like you would say with C code); instead, you are changing it into a syntax that exists at a similar level of abstraction so it can be used in the same way, but in slightly different circumstances (in this case, transforming one flavor of JavaScript into another).</p> +</div> + +<p>So for example, we talked about arrow functions (see <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/javascript/arrow-function.html">arrow-function.html</a> live, and see the <a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/javascript/arrow-function.html">source code</a>) earlier in the article, which only work in the newest browsers:</p> + +<pre class="brush: js">() => { ... }</pre> + +<p>We could transpile this across to a traditional old-fashioned anonymous function, so it would work in older browsers:</p> + +<pre class="brush: js">function() { ... }</pre> + +<p>The recommended tool for JavaScript transpiling is currently <a href="https://babeljs.io/">Babel</a>. This offers transpilation capabilities for language features that are appropriate for transpilation. For features that can't just be easily transpiled into an older equivalent, Babel also offers polyfills to provide support.</p> + +<p>The easiest way to give Babel a try is to use the <a href="https://babeljs.io/repl/">online version</a>, which allows you to enter your source code on the left, and outputs a transpiled version on the right.</p> + +<div class="note"> +<p><strong>Note</strong>: There are many ways to use Babel (task runners, automation tools, etc.), as you'll see on the <a href="https://babeljs.io/docs/setup/">setup page</a>.</p> +</div> + +<h3 id="Using_bad_browser_sniffing_code">Using bad browser sniffing code</h3> + +<p>All browsers have a <strong>user-agent</strong> string, which identifies what the browser is (version, name, OS, etc.) In the bad only days when pretty much everyone used Netscape or Internet Explorer, developers used to use so-called <strong>browser sniffing code</strong> to detect which browser the user was using, and give them appropriate code to work on that browser.</p> + +<p>The code used to look something like this (although this is a simplified example):</p> + +<pre class="brush: js">var ua = navigator.userAgent; + +if(ua.indexOf('Firefox') !== -1) { + // run Firefox-specific code +} else if(ua.indexOf('Chrome') !== -1) { + // run Chrome-specific code +}</pre> + +<p>The idea was fairly good — detect what browser is viewing the site, and run code as appropriate to make sure the browser will be able to use your site OK.</p> + +<div class="note"> +<p><strong>Note</strong>: Try opening up your JavaScript console now and running navigator.userAgent, to see what you get returned.</p> +</div> + +<p>However, as time went on, developers started to see major problems with this approach. For a start, the code was error prone. What if you knew a feature didn't work in say, Firefox 10 and below, and implemented code to detect this, and then Firefox 11 came out — which did support that feature? Firefox 11 probably wouldn't be supported because it's not Firefox 10. You'd have to change all your sniffing code regularly.</p> + +<p>Many developers implemented bad browser sniffing code and didn't maintain it, and browsers start getting locked out of using websites containing features that they had since implemented. This became so common that browsers started to lie about what browser they were in their user-agent strings (or claim they were all browsers), to get around sniffing code. Browsers also implemented facilities to allow users to change what user-agent string the browser reported when queried with JavaScript. This all made browser sniffing even more error prone, and ultimately pointless. </p> + +<div class="note"> +<p><strong>Note</strong>: You should read <a href="http://webaim.org/blog/user-agent-string-history/">History of the browser user-agent string</a> by Aaron Andersen for a useful and amusing take on this situation.</p> +</div> + +<p>The lesson to be learned here is — NEVER use browser sniffing. The only really use case for browser sniffing code in the modern day is if you are implementing a fix for a bug in a very specific version of a particular browser. But even then, most bugs get fixed pretty quickly in browser vendor rapid release cycles. It won't come up very often. {{anch("Feature detection")}} is almost always a better option — if you detect whether a feature is supported, you won't need to change your code when new browser versions come out, and the tests are much more reliable.</p> + +<p>If you come across browser sniffing when joining an existing project, look at whether it can be replaced with something more sensible. Browser sniffing causes all kind of interesting bugs, like {{bug(1308462)}}.</p> + +<h3 id="Handling_JavaScript_prefixes">Handling JavaScript prefixes</h3> + +<p>In the previous article, we included quite a lot of discussion about <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Handling_CSS_prefixes">handing CSS prefixes</a>. Well, new JavaScript implementations sometimes use prefixes too, although JavaScript uses camel case rather than hyphenation like CSS. For example, if a prefix was being used on a new shint API object called <code>Object</code>:</p> + +<ul> + <li>Mozilla would use <code>mozObject</code></li> + <li>Chrome/Opera/Safari would use <code>webkitObject</code></li> + <li>Microsoft would use <code>msObject</code></li> +</ul> + +<p>Here's an example, taken from our <a href="http://mdn.github.io/violent-theremin/">violent-theremin demo</a> (see <a href="https://github.com/mdn/violent-theremin">source code</a>), which uses a combination of the <a href="/en-US/docs/Web/API/Canvas_API">Canvas API</a> and the <a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a> to create a fun (and noisy) drawing tool:</p> + +<pre class="brush: js">var AudioContext = window.AudioContext || window.webkitAudioContext; +var audioCtx = new AudioContext();</pre> + +<p>In the case of the Web Audio API, the key entry points to using the API were supported in Chrome/Opera via <code>webkit</code> prefixed versions (they now support the unprefixed versions). The easy way to get around this situation is to create a new version of the objects that are prefixed in some browsers, and make it equal to the non-prefixed version, OR the prefixed version (OR any other prefixed versions that need consideration) — whichever one is supported by the browser currently viewing the site will be used.</p> + +<p>Then we use that object to manipulate the API, rather than the original one. In this case we are creating a modified <a href="/en-US/docs/Web/API/AudioContext">AudioContext</a> constructor, then creating a new audio context instance to use for our Web Audio coding.</p> + +<p>This pattern can be applied to just about any prefixed JavaScript feature. JavaScript libraries/polyfills also make use of this kind of code, to abstract browser differences away from the developer as much as possible.</p> + +<p>Again, prefixed features were never supposed to be used in production websites — they are subject to change or removal without warning, and cause cross browser issues. If you insist on using prefixed features, make sure you use the right ones. You can look up what browsers require prefixes for different JavaScript/API features on MDN reference pages, and sites like <a href="http://caniuse.com/">caniuse.com</a>. If you are unsure, you can also find out by doing some testing directly in browsers.</p> + +<p>For example, try going into your browser's developer console and start typing</p> + +<pre class="brush: js">window.AudioContext</pre> + +<p>If this feature is supported in your browser, it will autocomplete.</p> + +<h2 id="Finding_help">Finding help</h2> + +<p>There are many other issues you'll encounter with JavaScript; the most important thing to know really is how to find answers online. Consult the HTML and CSS article's <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#Finding_help">Finding help section</a> for our best advice.</p> + +<h2 id="Summary">Summary</h2> + +<p>So that's JavaScript. Simple huh? Maybe not so simple, but this article should at least give you a start, and some ideas on how to tackle the JavaScript-related problems you will come across.</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS","Learn/Tools_and_testing/Cross_browser_testing/Accessibility", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<p> </p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">Introduction to cross browser testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">Strategies for carrying out testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">Handling common HTML and CSS problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">Handling common JavaScript problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">Handling common accessibility problems</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">Implementing feature detection</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">Introduction to automated testing</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">Setting up your own test automation environment</a></li> +</ul> + +<p> </p> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/your_own_automation_environment/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/your_own_automation_environment/index.html new file mode 100644 index 0000000000..d1210ff118 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/your_own_automation_environment/index.html @@ -0,0 +1,641 @@ +--- +title: Setting up your own test automation environment +slug: Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenu("Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">在这篇文章中,我们将教会您如何安装自己的自动化测试环境,并使用Selenium/WebDriver,和一种测试库(比如:selenium-webdriver for Node)运行自己的测试。我们还将着眼于如何将本地测试环境与上一篇文章中讨论的商业工具集成在一起。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">Prerequisites:</th> + <td>Familiarity with the core <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> languages; an idea of the high level <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">principles of cross browser testing</a>, and <a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">automated testing</a>.</td> + </tr> + <tr> + <th scope="row">Objective:</th> + <td>To show how to set up a Selenium testing environment locally and run tests with it, and how to integrate it with tools like Sauce Labs and BrowserStack.</td> + </tr> + </tbody> +</table> + +<h2 id="Selenium">Selenium</h2> + +<p><a href="http://www.seleniumhq.org/">Selenium</a>是最流行的浏览器自动化测试工具。最易使用的方法是使用基于Selenium的WebDriver API,它通过调用浏览器接口实现自动化,执行诸如“打开网页”、“移动网页上的元素”、“点击链接”、“查看链接是否打开URL”等。对于运行自动化测试来说是一个十分理想的方法。</p> + +<p>安装和使用WebDriver的方式取决于你的编程环境。常见的环境都提供有安装WebDriver的包或框架,并且支持与WebDriver通信的多语言绑定,如 Java, C#, Ruby, Python, JavaScript (Node)等。查看<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp#setting-up-a-selenium-webdriver-project">建立一个Selenium-WebDriver的工程</a>了解Selenium在不同语言下建立的更多细节。</p> + +<p>不同的浏览器需要使用不同的驱动,来使WebDriver能够与浏览器交互并控制浏览器。查看<a href="http://www.seleniumhq.org/about/platforms.jsp">支持Selenium的平台</a>来了解获取浏览器驱动的信息。</p> + +<p>我们将使用Node.js来编写和运行Selenium测试用例。Node.js是一个前端开发者都很熟悉,并且容易上手的开发环境。</p> + +<div class="note"> +<p><strong>注意</strong>: 如果你需要了解在其他服务器端环境下使用WebDriver的方式,也可以点击<a href="http://www.seleniumhq.org/about/platforms.jsp">支持Selenium的平台</a>来获取更多有用的链接。</p> +</div> + +<h3 id="在Node下建立Selenium">在Node下建立Selenium</h3> + +<ol> + <li>参考上一个章节<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing#Setting_up_Node_and_npm">创建Node和npm</a>,创建一个新的npm工程,取一个不同的名字, 如<code>selenium-test</code>.</li> + <li>接下来,我们需要安装一个框架来允许我们从Node中运行Selenium。我们选择selenium官方提供的 <a href="https://www.npmjs.com/package/selenium-webdriver">selenium-webdriver</a>, 它的文档更新得很好,维护得也很好。如果你想要其它的选择, <a href="http://webdriver.io/">webdriver.io</a> 和 <a href="http://nightwatchjs.org/">nightwatch.js</a>也都不错。要安装 selenium-webdriver,在你的工程目录下运行如下命令: + <pre class="brush: bash"><code>npm install selenium-webdriver</code></pre> + </li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 即使你已经安装过 selenium-webdriver并下载了浏览器驱动,我们仍然建议你按照步骤再来一遍,确定所有东西都是最新的。</p> +</div> + +<p>接下来,你需要下载相应的驱动,使WebDriver能控制你需要测试的浏览器. 在<a href="https://www.npmjs.com/package/selenium-webdriver">selenium-webdriver</a> (第一部分的表格)页面查看如何下载。 显然,有的浏览器是OS定制的,我们将使用 Firefox 和 Chrome, 它们在主流的OS下都支持。</p> + +<ol> + <li>下载最新版本的 <a href="https://github.com/mozilla/geckodriver/releases/">GeckoDriver</a> ( Firefox) 和 <a href="http://chromedriver.storage.googleapis.com/index.html">ChromeDriver</a> 驱动.</li> + <li>将它们解压到一个简单的目录下,如用户根目录。</li> + <li>把 <code>chromedriver</code> 和 <code>geckodriver</code> 驱动的目录添加到你的系统 <code>PATH</code> 变量。这应该是从你的硬盘根目录开始的一个绝对路径。举个例子,如果我们使用的是一个 Mac OS X 机器, 用户名为 bob, 我们把驱动放到了用户的根目录下,那这个路径就是 <code>/Users/bob</code>。</li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: 重申一下,添加到 <code>PATH</code> 的路径是到包含驱动的那一级目录,而不是驱动目录自身! 这是一个常犯的错误。</p> +</div> + +<p>在Mac OS X或大多数Linux 系统中设置<code>PATH</code> 变量的操作如下:</p> + +<ol> + <li>打开 <code>.bash_profile</code> (或 <code>.bashrc</code>) 文件 (如果看不到隐藏文件, 需要将它们显示出来, 查看<a href="http://ianlunn.co.uk/articles/quickly-showhide-hidden-files-mac-os-x-mavericks/"> Mac OSX 显示/隐藏文件</a> 或 <a href="http://askubuntu.com/questions/470837/how-to-show-hidden-folders-in-ubuntu-14-04">Ubuntu 显示隐藏文件夹</a>)。</li> + <li>把下面语句粘贴到文件的最后 (就像平常在机器上更新path): + <pre class="brush: bash">#Add WebDriver browser drivers to PATH + +export PATH=$PATH:/Users/bob</pre> + </li> + <li>保存并关闭文件, 然后重启命令终端以生效Bash的配置。</li> + <li>在命令终端上敲下面命令,查看新的路径是否已经添加到 <code>PATH</code> 变量中: + <pre class="brush: bash">echo $PATH</pre> + </li> + <li>你应该可以在打印出来的信息中找到。</li> +</ol> + +<p>在Windows下设置 <code>PATH</code> 变量, 参考 <a href="http://windowsitpro.com/systems-management/how-can-i-add-new-folder-my-system-path">如何添加一个新的文件目录到系统路径?</a></p> + +<p>OK,现在我们来做一个快速的测试来验证一下一切是否正常。</p> + +<ol> + <li>在你的工程目录下创建一个新的文件<code>google_test.js</code>:</li> + <li>将下面代码复制到文件中保存: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +var driver = new webdriver.Builder() + .forBrowser('firefox') + .build(); + +driver.get('http://www.google.com'); + +driver.findElement(By.name('q')).sendKeys('webdriver'); + +driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); + +driver.findElement(By.name('btnK')).click(); + +driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +}); + +driver.quit();</pre> + </li> + <li>在终端上,记住在你的工程目录下,输入如下命令: + <pre class="brush: bash">node google_test</pre> + </li> +</ol> + +<p>你会看到Firefox自动打开了一个窗口!Google自动加载到tab中,"webdriver" 被输入到搜索框,然后搜索按钮被点击。WebDriver 等待2秒; 然后获取文本标题, 如果标题是"webdriver - Google Search", 将返回测试成功的消息。然后WebDriver 关闭 Firefox 窗口并结束。</p> + +<h2 id="一次测试多个浏览器">一次测试多个浏览器</h2> + +<p>接下来,让我们来试一下同时在多个浏览器上进行测试。这也是你经常碰到的情况!</p> + +<ol> + <li>在你的工程目录下创建另外一个新文件<code>google_test_multiple.js</code>.你可以根据实际需要测试的浏览器情况,对我们添加的浏览器进行修改或删除等操作。但确保系统安装了正确的浏览器驱动。关于如何填写<code>.forBrowser()</code> 方法中对浏览器描述的字符串,请参考 <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Browser.html">Browser enum</a> .</li> + <li>将下面代码复制到文件中保存: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +var driver_fx = new webdriver.Builder() + .forBrowser('firefox') + .build(); + +var driver_chr = new webdriver.Builder() + .forBrowser('chrome') + .build(); + +searchTest(driver_fx); +searchTest(driver_chr); + +function searchTest(driver) { + driver.get('http://www.google.com'); + driver.findElement(By.name('q')).sendKeys('webdriver'); + + driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); + }); + + driver.findElement(By.name('btnK')).click(); + + driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); + }); + + driver.quit(); +}</pre> + </li> + <li>在终端上,记住在你的工程目录下,输入如下命令: + <pre class="brush: bash">node google_test_multiple</pre> + </li> + <li>如果你用的是Mac测试Safari,可能会产生一个错误信息: "Could not create a session: You must enable the 'Allow Remote Automation' option in Safari's Develop menu to control Safari via WebDriver." 如果是这样,根据指示重新尝试一遍。</li> +</ol> + +<p>现在,我们像上次一样完成了测试,这一次浏览器的测试代码放到了<code>searchTest()</code>函数中。我们对多个浏览器创建了新的浏览器实例,然后将每一个实例传递给函数,这样就可以在3个浏览器下执行测试!</p> + +<p>有意思吧? 接下来我们继续,来了解一下WebDriver 的语法基础。</p> + +<h2 id="WebDriver语法速成课程">WebDriver语法速成课程</h2> + +<p>现在我们来看一下webdriver的一些关键语法。更完整的细节,可以参考<a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/">selenium-webdriver JavaScript API 参考</a> , 以及 Selenium 主要的文档<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp">Selenium WebDriver</a> 和 <a href="http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp">WebDriver: 高级使用手册</a> , 里面有用不同语言编写的丰富的学习示例。</p> + +<h3 id="启动一个新的测试">启动一个新的测试</h3> + +<p>要启动一次新的测试,你需要包含如下的<code>selenium-webdriver</code> 代码模块:</p> + +<pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until;</pre> + +<p>接下来, 通过 <code>new webdriver.Builder()</code> 构造器来创建一个新的驱动实例,通过<code>forBrowser()</code> 方法指定测试的浏览器类型,最后调用 <code>build()</code> 来实际创建它。 (查看 <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html">Builder class reference</a> 了解更多).</p> + +<pre class="brush: js">var driver = new webdriver.Builder() + .forBrowser('firefox') + .build();</pre> + +<p>在<code>forBrowser()</code> 中,你还可以设置浏览器的更多配置选项,如版本和操作系统:</p> + +<pre class="brush: js">var driver = new webdriver.Builder() + .forBrowser('firefox', '46', 'MAC') + .build();</pre> + +<p>你还可以通过设置环境变量的方式来配置这些选项,如:</p> + +<pre class="brush: bash"><code>SELENIUM_BROWSER=firefox:46:MAC</code></pre> + +<p>让我们来创建一个新的测试验证一下。在你的 selenium 测试工程目录下, 新建一个文件 <code>quick_test.js</code>, 将下面代码赋值进去:</p> + +<pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +var driver = new webdriver.Builder() + .forBrowser('firefox') + .build();</pre> + +<h3 id="获取测试的document">获取测试的document</h3> + +<p>通过刚创建的驱动实例的 <code>get()</code> 方法,加载你需要测试的网页:</p> + +<pre class="brush: js"><span class="nx">driver</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'http://www.google.com'</span><span class="p">);</span></pre> + +<div class="note"> +<p><span class="p"><strong>注意</strong>: 可以查看 <a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html">WebDriver class reference</a> 了解这一部分提到内容的详情。</span></p> +</div> + +<p>你可以使用包含<code>file://</code> 的 URL 来指向需要测试的本地文件:</p> + +<pre class="brush: js">driver.get('file:///Users/chrismills/git/learning-area/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html');</pre> + +<p>或</p> + +<pre class="brush: js">driver.get('http://localhost:8888/fake-div-buttons.html');</pre> + +<p>使用一个远程的服务器地址代码会更灵活 — 当你启用远程服务器运行测试时,如果企图使用本地路径,代码会中断。 </p> + +<p>现在添加下面代码到<code>quick_test.js</code> 的最后:</p> + +<pre class="brush: js">driver.get('http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html');</pre> + +<h3 id="同document交互">同document交互</h3> + +<p>得到测试的 document 后,我们就需要同它进行交互操作了,比如获取特定的元素对它进行某种测试。WebDriver提供了<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp#locating-ui-elements-webelements">通过多种方法选择UI元素</a>,比如通过 ID, class, element name等等. 具体的选择通过 <code>findElement()</code> 方法完成, 只需要将选择的元素作为参数传给它即可. 举例来说, 通过ID选择一个元素:</p> + +<pre class="brush: js"><span class="kd">var</span> <span class="nx">element</span> <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">findElement</span><span class="p">(</span><span class="nx">By</span><span class="p">.</span><span class="nx">id</span><span class="p">(</span><span class="s1">'myElementId'</span><span class="p">));</span></pre> + +<p>通过CSS查找一个元素的最常用方法是使用 By.css 方法,它可以通过CSS选择器的方式来选择元素。把下面代码敲到 <code>quick_test.js</code> 的最后面:</p> + +<pre class="brush: js"><span class="kd">var</span> <span class="nx">button</span> <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">findElement</span><span class="p">(</span><span class="nx">By</span><span class="p">.</span><span class="nx">css</span><span class="p">(</span><span class="s1">'</span>button:nth-of-type(1)<span class="s1">'</span><span class="p">));</span></pre> + +<h3 id="测试element"><span class="p">测试element</span></h3> + +<p><span class="p">同 web documents 和 elements 交互的方式很多. 在</span>WebDriver的文档中有很多常用的例子,你可以从<a href="http://www.seleniumhq.org/docs/03_webdriver.jsp#getting-text-values">获取文本值</a>开始。</p> + +<p>比如获取button上的文本,可以这样操作:</p> + +<pre class="brush: js">button.getText().then(function(text) { + console.log('Button text is \'' + text + '\''); +});</pre> + +<p>把这段代码也添加到 <code>quick_test.js</code> 中.</p> + +<p>在你的工程目录下,运行测试:</p> + +<pre class="brush: bash"><span class="p">node quick_test.js</span></pre> + +<p><span class="p">你可以看到button的文本标签打印到控制台。</span></p> + +<p><span class="p">现在进一步尝试一下。将上面输入的代码删除,然后添加下面的这行代码:</span></p> + +<pre class="brush: js">button.click();</pre> + +<p>重新运行测试;button被点击,你会看到alert()框弹出来,这样就知道button是正常工作的。</p> + +<p>你还可以与弹出框进行交互。将下面代码添加到代码最后,再运行一下测试:</p> + +<pre class="brush: js">var alert = driver.switchTo().alert(); + +alert.getText().then(function(text) { + console.log('Alert text is \'' + text + '\''); +}); + +alert.accept();</pre> + +<p>接下来, 我们试一下在表单的一个元素中输入文本。添加如下代码并运行测试:</p> + +<pre class="brush: js"><span class="kd">var</span> input <span class="o">=</span> <span class="nx">driver</span><span class="p">.</span><span class="nx">findElement</span><span class="p">(</span><span class="nx">By</span><span class="p">.</span><span class="nx">id</span><span class="p">(</span><span class="s1">'input1'</span><span class="p">)); +input.</span>sendKeys('Filling in my form');</pre> + +<p>你可以提交一些使用常规<code>webdriver.Key</code>属性不能代表的按键操作。举个例子,下面我们使用指令在提交前将tab从input移走:</p> + +<pre class="brush: js">driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); +</pre> + +<h3 id="等待操作完成">等待操作完成</h3> + +<p>有时候,在进行下一步处理之前我们需要WebDriver等待一些操作完成。比如装载一个新的页面,在与页面元素交互之前,需要等待DOM完成加载,否则测试有可能会失败.</p> + +<p>例如在 <code>google_test.js</code> 中, 有这样一段代码块:</p> + +<pre class="brush: js">driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +});</pre> + +<p><code>sleep()</code> 方法的参数指明需要等待的毫秒时间 — 然后在时间完成时,调用<code>then()</code> 内的代码块. 在这里例子中,我们通过<code>getTitle()</code>方法获得当前页面的title,然后根据title的值返回成功或失败的消息.</p> + +<p>添加一个 <code>sleep()</code> 方法到我们的 <code>quick_test.js</code> 中 — 将最后的代码修改成如下代码块:</p> + +<pre class="brush: js">driver.sleep(2000).then(function() { + input.sendKeys('Filling in my form'); + input.getAttribute("value").then(function(value) { + if(value !== '') { + console.log('Form input editable'); + } + }); +});</pre> + +<p>WebDriver 等待2秒然后填充表单的文本框. 接下来我们使用<code>getAttribute()</code>获取它的<code>value</code>属性值,并对它进行测试(如是否为空),最后将测试结果打印出来.</p> + +<div class="note"> +<p><strong>注意</strong>: 还有一个方法叫 <code><a href="http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/webdriver_exports_WebDriver.html#wait">wait()</a></code>, 它是在一定的时间内对某个条件进行反复测试,然后再继续执行代码,它也使用了 <a href="https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/lib/until.html">util 库</a>, 其中定义了使用<code>wait()的常见条件</code>.</p> +</div> + +<h3 id="使用后关闭驱动">使用后关闭驱动</h3> + +<p>完成一次测试后,需要关闭所有打开的驱动实例,确保你的机器上不会出现一堆泛滥的浏览器实例。只需要调用驱动实例的 <code>quit()</code> 方法即可。现在将下面这行代码添加到你的 <code>quick_test.js</code> 最后:</p> + +<pre class="brush: js"><span class="nx">driver</span><span class="p">.</span><span class="nx">quit</span><span class="p">();</span></pre> + +<p><span class="p">运行测试文件,这次你会看到在测试执行完毕后浏览器被关掉了。这很有用,如果你不希望电脑被一堆浏览器实例搞挂。</span></p> + +<h2 id="测试最佳实践">测试最佳实践</h2> + +<p>有很多编写最佳测试的实践方法,你可以参考<a href="http://www.seleniumhq.org/docs/06_test_design_considerations.jsp">测试设计考虑</a>来了解一些背景. 总的来说,测试应该遵循如下几点:</p> + +<ol> + <li>使用好的定位策略: 当你同document交互时,确保你使用的定位器和页面对象是不变的 — 如对元素进行测试, 确保这个元素有固定的ID或页面位置,这样可以通过CSS选择器定位到它,不至于在下一个迭代就发生变化。尽可能让你的测试稳健,而不是有一点改动就会break。</li> + <li>写原子测试: 一个测试只测一件事,这样有利于跟踪哪一个测试文件执行的是哪种测试。举例来说, 前面的<code>google_test.js</code> 就只测试了一个简单的用例 — 页面的搜索结果标题是否正确。我们可以给它改一个名字,这样当我们添加更多的测试文件时它的作用会更直观 ,比如改为 <code>results_page_title_set_correctly.js</code> 。</li> + <li>写独立的测试:每一个测试只需要自己执行,不需要依赖其它的测试。</li> +</ol> + +<p>除此之外,我们还要提一下测试结果/测试报告 — 在前面的例子中,我们只是简单的把测试结果通过 <code>console.log()</code> 语句打印出来,这个完全只在JavaScript中完成, 你可以使用任何你想要的测试运行和报告系统,如<a href="https://mochajs.org/">Mocha</a>/<a href="http://chaijs.com/">Chai</a>/或者其它的组合。</p> + +<ol> + <li>举个例子, 将 <code><a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/selenium/mocha_test.js">mocha_test.js</a></code> 拷贝到你的工程目录下. 把它放到一个叫做 <code>test</code>的子文件夹内,这个例子使用了一长串的promise来执行测试的所有步骤 — WebDriver 使用这些基于 promise 的方法保证正常工作.</li> + <li>执行下面的命令在你的工程目录下安装mocha测试工具: + <pre class="brush: bash">npm install --save-dev mocha</pre> + </li> + <li>然后通过下面的命令运行测试(所有你放到test目录下的测试): + <pre class="brush: bash">mocha --no-timeouts</pre> + </li> + <li>使用<code>--no-timeouts</code> 参数确保测试不会因Mocha的3秒超时时限而中途失败退出。 </li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: <a href="https://github.com/saucelabs-sample-test-frameworks">saucelabs-sample-test-frameworks</a> 中包含了一些关于如何设置不同测试/断言工具组合的有用示例。</p> +</div> + +<h2 id="运行远程测试">运行远程测试</h2> + +<p>在远端服务器上运行测试并不比在本地执行测试要困难多少,只需要在创建驱动实例的时候多配置几个特征参数,如测试的浏览器类型、服务器地址和用户权限(如果需要) 就可以。</p> + +<h3 id="BrowserStack">BrowserStack</h3> + +<p>在BrowserStack 进行Selenium远程测试很简单,参考下面的代码示例:</p> + +<ol> + <li>在你的工程目录下, 新建一个文件<code>bstack_google_test.js</code>.</li> + <li>复制下面内容: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +// Input capabilities +var capabilities = { + 'browserName' : 'Firefox', + 'browser_version' : '56.0 beta', + 'os' : 'OS X', + 'os_version' : 'Sierra', + 'resolution' : '1280x1024', + 'browserstack.user' : '<code>YOUR-USER-NAME</code>', + 'browserstack.key' : '<code>YOUR-ACCESS-KEY</code>', + 'browserstack.debug' : 'true', + 'build' : 'First build' +}; + +var driver = new webdriver.Builder(). + usingServer('http://hub-cloud.browserstack.com/wd/hub'). + withCapabilities(capabilities). + build(); + +driver.get('http://www.google.com'); +driver.findElement(By.name('q')).sendKeys('webdriver'); + +driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); + +driver.findElement(By.name('btnK')).click(); + +driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +}); + +driver.quit();</pre> + </li> + <li>在 <a href="https://www.browserstack.com/automate">BrowserStack automation dashboard</a>, 获取你的用户名和Key (查看 <em>Username 和 Access Keys</em>),替换代码中 <code>YOUR-USER-NAME</code> 和<code>YOUR-ACCESS-KEY</code> 的对应值。(确保它们依然安全).</li> + <li>执行下面命令: + <pre class="brush: bash">node bstack_google_test</pre> + 测试被发送给 BrowserStackces, 测试结果会返回到你的控制台. 这体现了包含报告机制的重要性!</li> + <li>现在回到 <a href="https://www.browserstack.com/automate">BrowserStack automation dashboard</a> 页面, 你会看到测试列出来的结果:<br> + <img alt="" src="https://mdn.mozillademos.org/files/15383/bstack_automated_results.png" style="border-style: solid; border-width: 1px; display: block; height: 189px; margin: 0px auto; width: 700px;"></li> +</ol> + +<p>点击测试链接, 会打开一个屏幕播放记录了测试的视频,和在测试过程中相关的log详情。</p> + +<div class="note"> +<p><strong>Note</strong>: Browserstack 自动化仪表盘的Resources菜单选项上 提供了许多运行自动化测试的有用信息。查看 <a href="https://www.browserstack.com/automate/node">关于 Node JS编写自动化测试的文档</a> 获取相关信息. 研究一下使用 BrowserStack可以帮你做到哪些事情.</p> +</div> + +<div class="note"> +<p><strong>注意</strong>: 如果你不想自己写测试用例,可以使用docs中嵌入的生成器, 查看 <a href="https://www.browserstack.com/automate/node#run-tests-on-mobile">在手机浏览器上运行测试</a>和<a href="https://www.browserstack.com/automate/node#setting-os-and-browser">在桌面浏览器上运行测试</a>.</p> +</div> + +<h4 id="编程填充_BrowserStack_的测试详情">编程填充 BrowserStack 的测试详情</h4> + +<p>使用BrowserStack REST API 和其它功能可以给测试添加更多详情,如测试是否通过,为什么通过,测试属于工程的哪个部分等,BrowserStack 默认并没有这些细节!</p> + +<p>让我们来更新一下 <code>bstack_google_test.js</code> 示例,看看它们是怎样运作的:</p> + +<ol> + <li>首先, 导入node 需要的模块, 用来发送requests 给 REST API.在代码的顶端添加如下语句: + <pre class="brush: js">var request = require("request");</pre> + </li> + <li>现在更新一下 <code>capabilities</code> 对象,添加工程名 — 在右大括号前添加下面代码, 记得要在上一行末增加一个逗号(在 BrowserStack 自动化仪表盘上,你可以修改 build 和 project 名称来组织不同窗口下的测试): + <pre class="brush: js">'project' : 'Google test 2'</pre> + </li> + <li>接下来获取当前会话的 <code>sessionId</code> , 就知道往哪儿发送 request ( 后面你会看到,ID 包含在request 的URL中). 将下面代码添加到创建 <code>driver</code> 对象 (<code>var driver ...</code>) 的代码块下面: + <pre class="brush: js">var sessionId; + +driver.session_.then(function(sessionData) { + sessionId = sessionData.id_; +});</pre> + </li> + <li>最后, 修改下面 <code>driver.sleep(2000)</code> ... 的代码,添加 REST API 调用 (同样,使用你的真实用户名和key替换 <code>YOUR-USER-NAME</code> 和<code>YOUR-ACCESS-KEY</code> 的值): + <pre class="brush: js">driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + request({uri: "https://<code>YOUR-USER-NAME</code>:<code>YOUR-ACCESS-KEY</code>@www.browserstack.com/automate/sessions/" + sessionId + ".json", method:"PUT", form:{"status":"passed","reason":"Google results showed correct title"}}); + } else { + console.log('Test failed'); + request({uri: "https://<code>YOUR-USER-NAME</code>:<code>YOUR-ACCESS-KEY</code>@www.browserstack.com/automate/sessions/" + sessionId + ".json", method:"PUT", form:{"status":"failed","reason":"Google results showed wrong title"}}); + } + }); +});</pre> + </li> +</ol> + +<p>代码很直观 — 测试一完成, 就会发送一个 API 调用到 BrowserStack 来更新测试状态是通过还是完成,并且给出相关的原因。</p> + +<p>现在回到 <a href="https://www.browserstack.com/automate">BrowserStack 自动化仪表盘</a> 页面, 你会看到测试会话如之前一样正常运行,并且增加了更新的数据:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15385/bstack_custom_results.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<h3 id="Sauce_Labs">Sauce Labs</h3> + +<p>在Sauce Labs 远程运行Selenium测试与在BrowserStack一样简单, 尽管它们有一些语法的差异. 代码示例如下:</p> + +<ol> + <li>在你的工程目录下, 新建一个文件 <code>sauce_google_test.js</code>.</li> + <li>复制下面内容: + <pre class="brush: js">var webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until, + username = "YOUR-USER-NAME", + accessKey = "YOUR-ACCESS-KEY"; + +var driver = new webdriver.Builder() + .withCapabilities({ + 'browserName': 'chrome', + 'platform': 'Windows XP', + 'version': '43.0', + 'username': username, + 'accessKey': accessKey + }) + .usingServer("https://" + username + ":" + accessKey + + "@ondemand.saucelabs.com:443/wd/hub") + .build(); + +driver.get('http://www.google.com'); + +driver.findElement(By.name('q')).sendKeys('webdriver'); + +driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); + +driver.findElement(By.name('btnK')).click(); + +driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + } else { + console.log('Test failed'); + } + }); +}); + +driver.quit();</pre> + </li> + <li>从你的 <a href="https://saucelabs.com/beta/user-settings">Sauce Labs 用户设置</a>, 获取你的用户名和key. 并替换代码中对于 <code>YOUR-USER-NAME</code> 和<code>YOUR-ACCESS-KEY</code> 的值 (确保它们依然安全).</li> + <li>执行如下命令运行测试: + <pre class="brush: bash">node sauce_google_test</pre> + 测试会被发送到, 并返回相应的测试结果到你的控制台. 这体现了包含报告机制的重要性!</li> + <li>现在访问你的 <a href="https://saucelabs.com/beta/dashboard/tests">Sauce Labs 自动化测试仪表盘</a> 页面, 会看到列出的测试; 同样你也可以看到视频、截屏和其他类似的数据。<br> + <img alt="" src="https://mdn.mozillademos.org/files/14235/sauce_labs_automated_test.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></li> +</ol> + +<div class="note"> +<p><strong>注意</strong>: Sauce Labs' <a href="https://wiki.saucelabs.com/display/DOCS/Platform+Configurator/#/">Platform Configurator</a> is a useful tool for generating capability objects to feed to your driver instances, based on what browser/OS you want to test on.</p> +</div> + +<div class="note"> +<p><strong>注意</strong>: 你可以查看 <a href="https://wiki.saucelabs.com/display/DOCS/Getting+Started+with+Selenium+for+Automated+Website+Testing">使用 Selenium 进行Web自动化测试</a>, 和 <a href="https://wiki.saucelabs.com/display/DOCS/Instant+Selenium+Node.js+Tests">实时 Selenium Node.js 测试</a>获取更多关于Sauce Labs和Selenium测试的有用信息。</p> +</div> + +<h4 id="编程填充_Sauce_Labs_的测试详情">编程填充 Sauce Labs 的测试详情</h4> + +<p>使用Sauce Labs API 可以给测试添加更多详情,如测试是否通过,测试名称等等,Sauce Labs 默认并没有这些细节!</p> + +<p>示例如下:</p> + +<ol> + <li>安装 Node Sauce Labs 套件 (如果你之前没有运行过): + <pre class="brush: bash"><code class="language-html">npm install saucelabs --save-dev</code></pre> + </li> + <li>Require saucelabs — 在 <code>sauce_google_test.js</code> 文件顶端, 就在之前的变量声明下面添加如下代码: + <pre class="brush: js line-numbers language-js"><code class="language-js"><span class="keyword token">var</span> SauceLabs <span class="operator token">=</span> <span class="function token">require</span><span class="punctuation token">(</span><span class="string token">'saucelabs'</span><span class="punctuation token">)</span><span class="punctuation token">;</span></code></pre> + </li> + <li>接着添加如下代码,创建一个新的SauceLabs实例: + <pre class="brush: js">var saucelabs = new SauceLabs({ + username : "YOUR-USER-NAME", + password : "YOUR-ACCESS-KEY" +});</pre> + 同样, 用真实的用户名和key来替换<code>YOUR-USER-NAME</code> 和 <code>YOUR-ACCESS-KEY</code> 的值 (注意 saucelabs 的npm 包使用的是 <code>password</code>, 而不是<code>accessKey</code>). Since you are using these twice now, you may want to create a couple of helper variables to store them in.</li> + <li>在 <code>driver</code> 变量定义的下方(就在 <code>build()</code> 行后面), 添加下面代码块 — 获取正确的驱动 <code>sessionID</code> 来将数据写入进程 (在后面的代码中操作): + <pre class="brush: js">driver.getSession().then(function (sessionid){ + driver.sessionID = sessionid.id_; +});</pre> + </li> + <li>最后,将代码末尾的 replace the <code>driver.sleep(2000)</code> ... 替换如下: + <pre class="brush: js">driver.sleep(2000).then(function() { + driver.getTitle().then(function(title) { + if(title === 'webdriver - Google Search') { + console.log('Test passed'); + var testPassed = true; + } else { + console.log('Test failed'); + var testPassed = false; + } + + saucelabs.updateJob(driver.sessionID, { + name: 'Google search results page title test', + passed: testPassed + }); + }); +});</pre> + </li> +</ol> + +<p>这里,我们根据测试结果是通过或失败,将 <code>testPassed</code> 变量设置为<code>true</code> 或 <code>false</code> , 然后使用 <code>saucelabs.updateJob()</code> 方法来更新详情。</p> + +<p>回到 <a href="https://saucelabs.com/beta/dashboard/tests">Sauce Labs 自动化测试仪表盘</a> 页面, 你会看到新的进程更新了信息:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14239/sauce_labs_updated_job_info.png" style="border-style: solid; border-width: 1px; display: block; height: 150px; margin: 0px auto; width: 1088px;"></p> + +<h3 id="您自己的远程服务器">您自己的远程服务器</h3> + +<p>如果你不想使用Sauce Labs或BrowserStack之类的服务,你也可以设置自己的远程测试服务器。具体操作如下:</p> + +<ol> + <li>Selenium 的远端服务器要求运行Java,从<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html"> Java SE下载页面</a>下载适合你平台的最新JDK并安装.</li> + <li>接着, 下载最新的 <a href="http://selenium-release.storage.googleapis.com/index.html">Selenium 单机服务器</a> — 它作为你的脚本和浏览器驱动之间的一个代理. 选择最新的稳定版本 (比如不要选一个 beta版本), 从列表中选择以 "selenium-server-standalone"开头的文件. 下载完成后,放到一个靠谱的地方,比如home目录下. 如果你还没有将位置添加到 <code>PATH</code>, 现在就需要添加了(查看 "在Node中创建 Selenium"章节).</li> + <li>在作为服务器的电脑终端上执行如下代码,安装单机版服务器 + <pre class="brush: bash"><code>java -jar </code>selenium-server-standalone-3.0.0.jar</pre> + (修改 <code>.jar</code> 文件名为你获取的文件名称)</li> + <li>服务会运行在 <code><a href="http://localhost:4444/wd/hub">http://localhost:4444/wd/hub</a></code> — 你可以去试试看是什么效果.</li> +</ol> + +<p>现在服务器运行起来了,让我们在selenium服务器上来创建一个demo测试.</p> + +<ol> + <li>复制 <code>google_test.js</code> 文件, 改名为 <code>google_test_remote.js</code>; 放到工程目录下.</li> + <li>将第二段代码 ( <code>var driver = </code>开头的代码段) 修改如下 + <pre class="brush: js">var driver = new webdriver.Builder() + .forBrowser('firefox') + .usingServer('http://localhost:4444/wd/hub') + .build();</pre> + </li> + <li>运行测试,你会看到它如期执行 ; 只不过这次是在服务器端: + <pre class="brush: bash">node google_test_remote.js</pre> + </li> +</ol> + +<p>是不是很酷. 我们是在本地测试的,但你可以任何一台服务器上使用相关的浏览器驱动进行测试,使用相应的URL将你的脚本和服务器连接起来就可以了.</p> + +<h2 id="将selenium和CI工具集成">将selenium和CI工具集成</h2> + +<p>另外,将Selenium同Sauce Labs之类的其它工具集成到持续集成(CI)工具中是很有用的,这意味着你可以通过CI工具来运行测试,只有测试通过才允许提交代码的修改。</p> + +<p>在这里不对这个话题进行深入探讨,但是我们建议你从Travis CI开始 — 这大概是最容易入门的CI工具了,它与GitHub和Node等web工具都有很好的集成。</p> + +<p>作为入门, 可以查看一些例子:</p> + +<ul> + <li><a href="https://docs.travis-ci.com/user/for-beginners">Travis CI for complete beginners</a></li> + <li><a href="https://docs.travis-ci.com/user/languages/javascript-with-nodejs/">Building a Node.js project</a> (with Travis)</li> + <li><a href="https://docs.travis-ci.com/user/sauce-connect/">Using Sauce Labs with Travis CI</a></li> +</ul> + +<h2 id="总结">总结</h2> + +<p>This module should have proven fun, and should have given you enough of an insight into writing and running automated tests for you to get going with writing your own automated tests.</p> + +<p>{{PreviousMenu("Learn/Tools_and_testing/Cross_browser_testing/Automated_testing", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/可访问性/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/可访问性/index.html new file mode 100644 index 0000000000..704f595fb4 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/可访问性/index.html @@ -0,0 +1,624 @@ +--- +title: 解决常见的可访问性问题 +slug: Learn/Tools_and_testing/Cross_browser_testing/可访问性 +tags: + - CSS + - CodingScripting + - HTML + - JavaScript + - 初学者 + - 可访问性 + - 学习 + - 工具 + - 文章 + - 测试 + - 跨浏览器 + - 键盘 +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Accessibility +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/JavaScript","Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary">接下来,我们将关注可访问性,提供关于一些常见问题的信息,如何进行简单测试以及如何使用审核/自动化工具来查找可访问性问题。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提:</th> + <td>熟悉<a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>,和<a href="/en-US/docs/Learn/JavaScript">JavaScript</a>语言; 理解<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试原理</a></td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>能够诊断常见的可访问性问题,并使用适当的工具和技术解决问题。</td> + </tr> + </tbody> +</table> + +<h2 id="什么是可访问性?">什么是可访问性?</h2> + +<p>当我们在web技术的背景下说可访问性时,大多数人立即想到确保残疾人可以使用网站/应用程序,例如:</p> + +<ul> + <li>视障人士使用屏幕阅读器或放大/缩放浏览文本</li> + <li>有运动功能障碍的人使用键盘(或其他非鼠标功能)使用网站。</li> + <li>有听力障碍的人依赖于替代音频/视频内容的文本。</li> +</ul> + +<p>但是,说可访问性仅与残疾人有关是错误的。实际上,可访问性的目的是使你的网站/应用程序在尽可能多的环境中被尽可能多的人使用,而不仅仅是那些使用高性能台式计算机的用户。极端的例子可能包括:</p> + +<ul> + <li>移动端用户</li> + <li>使用其他浏览设备(例如电视,手表等)</li> + <li>使用较旧设备的用户(他们可能没有最新的浏览器)</li> + <li>设备性能不高的用户(他们可能具有较慢的处理器)</li> +</ul> + +<p>在某种程度上,本模块都是关于可访问性的 — 跨浏览器测试可确保你的网站可以被尽可能多的人使用。这篇<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Accessibility/What_is_accessibility">可访问性是什么?</a> 更全面透彻地定义了什么是可访问性。</p> + +<p>也就是说,本文将涵盖跨浏览器和有关残疾人的测试问题以及他们如何使用Web。我们在其他地方已经讨论过其他领域,例如<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#%E5%93%8D%E5%BA%94%E5%BC%8F%E8%AE%BE%E8%AE%A1%E9%97%AE%E9%A2%98">响应式设计问题</a>和<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript#Performance_issues">性能</a>。</p> + +<div class="note"> +<p><strong>Note</strong>: 就像Web开发中的许多事情一样,可访问性不是100%的成功或失败可以定义的;对于所有内容而言,几乎不可能实现100%的可访问性,尤其是当站点变得越来越复杂时。我们更多的是通过防御性编码并遵循最佳实践,努力使尽可能多的人可以访问尽可能多的内容。</p> +</div> + +<h2 id="常见可访问性问题">常见可访问性问题</h2> + +<p>在本节中,我们将围绕Web可访问性,详细介绍与特定技术相关的一些主要问题、要遵循的最佳实践,以及可以进行的一些快速测试,以查看你的网站是否朝着正确的方向发展。</p> + +<div class="note"> +<p><strong>Note</strong>: 可访问性在道德上是正确的事情,对企业也有好处(残疾用户,移动用户等构成了重要的细分市场), 并且在世界许多地方,提供出来的网络媒体资源无法为残疾人服务也是违法的。阅读<a href="https://developer.mozilla.org/zh-CN/docs/Learn/Accessibility/What_is_accessibility#%E6%97%A0%E9%9A%9C%E7%A2%8D%E6%8C%87%E5%8D%97%E5%92%8C%E6%B3%95%E5%BE%8B">无障碍指南和法律</a>获取更多相关信息。</p> +</div> + +<h3 id="HTML">HTML</h3> + +<p>HTML语义化 (语义化正确地使用HTML标签)对于可访问性来说是开箱即用的 — 这类内容可供无视障人士阅读(前提是你不会做任何愚蠢的事情,例如使文本变小或使用CSS隐藏它),也可被屏幕阅读器(从字面上读出网页的应用)之类的辅助技术使用,并赋予其他优势。</p> + +<h4 id="语义化结构">语义化结构</h4> + +<p>HTML语义化最重要的捷径是为你的内容使用标题和段落的结构;这是因为屏幕阅读器用户倾向于将文档标题用作导航,以更快地找到他们需要的内容。如果你的内容没有标题,那么他们将获得的是一大坨文字,没有任何可定位的标记。坏的例子和好的例子如下:</p> + +<pre class="brush: html example-bad"><font size="7">My heading</font> +<br><br> +This is the first section of my document. +<br><br> +I'll add another paragraph here too. +<br><br> +<font size="5">My subheading</font> +<br><br> +This is the first subsection of my document. I'd love people to be able to find this content! +<br><br> +<font size="5">My 2nd subheading</font> +<br><br> +This is the second subsection of my content. I think is more interesting than the last one.</pre> + +<pre class="brush: html example-good"><h1>My heading</h1> + +<p>This is the first section of my document.</p> + +<p>I'll add another paragraph here too.</p> + +<h2>My subheading</h2> + +<p>This is the first subsection of my document. I'd love people to be able to find this content!</p> + +<h2>My 2nd subheading</h2> + +<p>This is the second subsection of my content. I think is more interesting than the last one.</p></pre> + +<p>此外,你的内容应该在逻辑顺序上讲得通的 — 你总能在以后再为它们写CSS,但你应该在一开始就确定内容正确的顺序。</p> + +<p>作为测试,你可以关闭网站的CSS,然后看看没有了CSS网站是否能被理解。你可以通过从代码中删除CSS来手动完成此操作,但是最简单的方法是使用浏览器功能,例如:</p> + +<ul> + <li>Firefox: 选择<em>查看 > 页面样式 > 无样式</em></li> + <li>Safari: 选择<em>开发 > 停用样式</em> (需要开启“开发”菜单, 点击<em>Safari > 偏好设置 > 高级 > 在菜单栏中显示“开发”菜单</em>)</li> + <li>Chrome: 安装Web Developer Toolbar扩展程序, 然后重启浏览器。点击图标,选择<em>CSS > 停用所有样式</em></li> + <li>Edge: 选择<em>查看 > 样式 > 无样式</em></li> +</ul> + +<h4 id="使用键盘">使用键盘</h4> + +<p>某些HTML功能可以使用键盘来选择 — 这是默认的,从早期web开始就是这样的。具有此功能的元素是允许用户与网页交互的常见元素,比如links, {{htmlelement("button")}}s, 以及表单元素,比如{{htmlelement("input")}}.</p> + +<p>浏览<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html">native-keyboard-accessibility.html</a> (查看<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/accessibility/native-keyboard-accessibility.html">源码</a>) 尝试一下— 在新标签页中打开它,然后尝试按Tab键;按下几下后,你应该看到标签焦点开始在不同的可聚焦元素之间移动;在每个浏览器中,被聚焦的元素都被赋予突出的默认样式 (不同的浏览器表现略有不同) 以便你能分辨聚焦在哪个元素上。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14215/button-focused-unfocused.png" style="border-style: solid; border-width: 1px; display: block; height: 39px; margin: 0px auto; width: 288px;"></p> + +<p>然后,你可以按Enter / Return键来关注焦点链接,或者按一个按钮(我们已经包含一些JavaScript来使按钮提醒消息),或者在输入框开始输入文本,(其他表单元素具有不同的控件,例如{{htmlelement("select")}}元素可以使用向上和向下箭头键显示和循环显示其选项)。</p> + +<p>请注意,不同的浏览器可能具有不同的键盘控制选项。大多数现代浏览器都遵循上述的标签模式(你也可以执行Shift + Tab来向后移动可聚焦元素),但是某些浏览器具有自己的特性:</p> + +<ul> + <li>Firefox for the Mac doesn't do tabbing by default. To turn it on, you have to go to <em>Preferences > Advanced > General</em>, then uncheck "Always use the cursor keys to navigate within pages". Next, you have to open your Mac's System Preferences app, then go to <em>Keyboard > Shortcuts</em>, then select the <em>All Controls</em> radio button.</li> + <li>Safari默认情况下不允许你按tab遍历链接;要启用此功能,你需要打开Safari的“<em>偏好设置</em>”,转到“<em>高级</em>”,然后选中“<em>按下Tab键以高亮显示网页上的每一项</em>”。</li> +</ul> + +<div class="warning"> +<p><strong>重要</strong>: 你应该在所写的任何新页面上执行这种测试 — 确保可以通过键盘使用功能。</p> +</div> + +<p>这个例子强调了正确使用语义元素的重要性。可以用CSS将任何元素的样式设置为看起来像链接或按钮,并使用JavaScript让其表现为像链接或按钮一样,但实际上它们不是链接或按钮,你将失去很多语义化元素带给你的可访问性。因此,尽量避免这样做。</p> + +<p>另一个技巧 — 如我们的示例所示,你可以使用<a href="/zh-CN/docs/Web/CSS/:focus">:focus</a>伪类控制可聚焦元素在聚焦时的外观。最好将焦点和悬停样式加重显示,这样无论是使用鼠标还是键盘的用户,都能直观地察觉控件在被激活时将执行的操作</p> + +<pre class="brush: css">a:hover, input:hover, button:hover, select:hover, +a:focus, input:focus, button:focus, select:focus { + font-weight: bold; +}</pre> + +<div class="note"> +<p><strong>Note</strong>: 如果你决定使用CSS删除默认的焦点样式,请确保将其替换为更适合你的设计的其他样式 — 这是一种非常有价值的可访问性工具,不应删除。</p> +</div> + +<h4 id="模拟键盘">模拟键盘</h4> + +<p>有时可能无法使用键盘完成可访问性。你可能有一个语义不是很好的网站(也许你最终得到了一个糟糕的CMS网页,该CMS生成了由<div> 组成的按钮),或者你正在使用一个没有内置键盘可访问性的复杂控件,例如HTML5 {{htmlelement("video")}} 元素(令人惊奇的是,Opera是唯一允许你在<video>元素的默认浏览器控件之间进行制表的浏览器)。你有几种选择:</p> + +<ol> + <li>使用<button>元素(默认情况下都是可以在button间使用Tab键)和JavaScript创建自定义控件,以连接其功能。有关此示例,请参见<a href="https://developer.mozilla.org/en-US/docs/Web/Guide/Audio_and_video_delivery/cross_browser_video_player">Creating a cross-browser video player</a>。</li> + <li>通过JavaScript创建键盘快捷键,因此当你按键盘上的某些键时,功能被激活。请参阅<a href="/en-US/docs/Games/Techniques/Control_mechanisms/Desktop_with_mouse_and_keyboard">Desktop mouse and keyboard controls</a>,以获取一些可用于任何目的(比如游戏)的例子。</li> + <li>使用一些有趣的策略来伪造按钮行为。以我们的<a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">fake-div-buttons.html</a>示例为例(<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/accessibility/fake-div-buttons.html">查看源码</a>)。这里我们通过为每个假按钮赋予属性<code>tabindex="0"</code>(请参阅WebAIM的<a href="http://webaim.org/techniques/keyboard/tabindex">tabindex文章</a>以获取更多详细信息),使假的<div>按钮能够被聚焦(包括通过制表符)。这使我们可以跳到按钮上,但不能通过回车键激活它们。为此,我们必须添加以下JavaScript代码: + <pre class="brush: js">document.onkeydown = function(e) { + if(e.keyCode === 13) { // The Enter/Return key + document.activeElement.onclick(e); + } +};</pre> + 在这里,我们向<code>document</code>对象添加了一个监听器,以检测何时按下了键盘上的按钮。我们通过事件对象的keyCode属性检查按下了什么按钮;如果它是与回车键匹配的键码,则使用<code>document.activeElement.onclick()</code>运行存储在按钮的onclick处理程序中的函数。<code>document.activeElement</code>为我们提供了当前页面上被聚焦的元素。</li> +</ol> + +<div class="note"> +<p><strong>Note</strong>: 仅当你通过事件处理程序属性(例如<code>onclick</code>)设置原始事件处理程序时,此技术才有效。<code>addEventListener</code>将无法正常工作。重新构建功能会有很多额外的麻烦。并且肯定还有其他问题。最好能从根源解决问题,使用正确的语义化元素。</p> +</div> + +<h4 id="替代文本">替代文本</h4> + +<p>替代文本对于可访问性非常重要 — 如果一个人有视觉或听觉障碍使他们无法看到或听到某些内容,那么这就是一个问题。可用的最简单的文本替代方法是<code>alt</code>属性,我们应该在所有包含相关内容的图像上包括该属性。其中应包含对图像的描述,该描述可在页面上成功传达其含义和内容,并由屏幕阅读器读取并读出给用户。</p> + +<div class="note"> +<p><strong>Note</strong>: 更多信息请阅读<a href="https://developer.mozilla.org/zh-CN/docs/learn/Accessibility/HTML:%E4%B8%BA%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%89%AF%E5%A5%BD%E7%9A%84%E5%9F%BA%E7%A1%80#%E6%96%87%E6%9C%AC%E6%9B%BF%E4%BB%A3%E5%93%81">Text alternatives</a></p> +</div> + +<p>可以通过多种方法来测试缺少的替代文本,例如,使用可访问性{{anch("审计工具")}}。</p> + +<p>对于视频和音频内容,Alt文本稍微复杂一些。有一种方法可以定义文本轨道(例如,字幕)并在播放视频时以{{htmlelement("track")}}元素和<a href="/zh-CN/docs/Web/API/WebVTT_API">WebVTT</a>格式的形式显示它们(请参见<a href="/zh-CN/docs/Web/Guide/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video">Adding captions and subtitles to HTML5 video</a>以获取详细信息)。这些功能的<a href="/zh-CN/docs/Web/Guide/Audio_and_video_delivery/Adding_captions_and_subtitles_to_HTML5_video#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9">浏览器兼容性</a>相当好,但是如果你想提供音频的替代文本或支持较旧的浏览器,则在页面某处或单独页面上显示一个简单的文本记录可能是个好主意。</p> + +<h4 id="元素关系和上下文">元素关系和上下文</h4> + +<p>HTML中有某些功能和最佳实践,旨在提供元素之间的上下文和关系。三个最常见的示例是链接,表单标签和数据表。</p> + +<p>使用屏幕阅读器的人们通常会使用一项共同的功能,即他们会拉出页面上所有链接的列表。在这种情况下,链接文本需要脱离上下文。例如,标记为“单击此处”,“单击此处”等的链接列表确实对可访问性不利。链接文本最好在上下文和上下文之外都有意义。</p> + +<p>表单{{htmlelement("label")}}元素是允许我们使表单可访问的主要功能之一。表单的麻烦在于,你需要标签来说明应在每个表单输入中输入哪些数据。每个标签都必须包含在{{htmlelement("label")}}内,以将其明确链接到其对应的表单输入框(属性值的每个<code><label></code>的<code>for</code>属性值必须与表单元素<code>id</code>值匹配),即使源顺序不是完全合乎逻辑的,也能提供很好的可访问性。</p> + +<div class="note"> +<p><strong>Note</strong>: 更多关于链接文本和表单标签,请阅读<a href="/zh-CN/docs/learn/Accessibility/HTML:%E4%B8%BA%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%89%AF%E5%A5%BD%E7%9A%84%E5%9F%BA%E7%A1%80#%E6%9C%89%E6%84%8F%E4%B9%89%E7%9A%84%E6%96%87%E5%AD%97%E6%A0%87%E7%AD%BE">有意义的文字标签</a></p> +</div> + +<p>最后,简要介绍一下数据表。基本数据表可以用非常简单的标记编写(请参阅<a href="http://mdn.github.io/learning-area/accessibility/html/bad-table.html">bad-table.html</a>和<a href="https://github.com/mdn/learning-area/blob/master/accessibility/html/bad-table.html">源码</a>)),但这存在问题 — 屏幕阅读器用户无法将行或列作为数据分组关联在一起,但你需要知道标题行是什么,以及标题行行的标题还是列的标题等。这些只能从可视化的表格才能知道。</p> + +<p>相反,如果你看一下我们的<code>punk-bands-complete.html</code>示例(<a href="https://mdn.github.io/learning-area/css/styling-boxes/styling-tables/punk-bands-complete.html">示例</a>,<a href="https://github.com/mdn/learning-area/blob/master/css/styling-boxes/styling-tables/punk-bands-complete.html">源码</a>),则可以在此处看到一些可访问性辅助,例如表头({{htmlelement("th")}}和<code>作用域</code>属性),{{htmlelement("caption")}}元素等。</p> + +<div class="note"> +<p><strong>Note</strong>: 更多信息,请阅读<a href="/zh-CN/docs/learn/Accessibility/HTML:%E4%B8%BA%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7%E6%8F%90%E4%BE%9B%E4%B8%80%E4%B8%AA%E8%89%AF%E5%A5%BD%E7%9A%84%E5%9F%BA%E7%A1%80#%E5%8F%AF%E8%AE%BF%E9%97%AE%E7%9A%84%E8%A1%A8%E6%A0%BC">可访问的表格</a></p> +</div> + +<h3 id="CSS">CSS</h3> + +<p>CSS往往提供的基本可访问性功能要比HTML少得多,但是如果使用不当,它仍然会对可访问性造成同样的损害。下面是一些涉及CSS的可访问性的点:</p> + +<ul> + <li>使用正确的语义元素修饰HTML中的不同内容;如果要创建不同的视觉效果,请使用CSS-不要滥用HTML元素来获得所需的外观。例如,如果你想要更大的文本,请使用{{cssxref("font-size")}},而不是{{htmlelement("h1")}}标签。</li> + <li>确保内容顺序在没有CSS的情况下有意义;你可以随时使用CSS设置页面样式。</li> + <li>你应该确保按钮和链接之类的交互元素具有适当的聚焦/悬停/活动状态设置,以便用户能姨一目了然。如果出于风格原因删除默认设置,请确保包括一些替代的样式。</li> +</ul> + +<p>还有其他几个需要注意的地方。</p> + +<h4 id="颜色和颜色对比度">颜色和颜色对比度</h4> + +<p>为你的网站选择配色方案时,应确保文本(前景)颜色与背景颜色形成鲜明对比。你的设计可能看起来很酷,但是如果视力障碍者(例如色盲)无法阅读你的内容,那就不好了。使用WebAIM的<a href="http://webaim.org/resources/contrastchecker/">Color Contrast Checker</a>之类的工具来检查你的方案是否有足够对比度。</p> + +<p>另一个提示是不要仅依靠颜色来表示界标/信息,因为这对于看不见颜色的人来说是不好的。例如,不要将所需的表单字段标记为红色,而应使用星号和红色标记它们。</p> + +<div class="note"> +<p><strong>Note</strong>: 高对比度也可以让使用带有光滑屏幕的设备(例如智能手机或平板电脑)的人在明亮的环境(例如阳光)下可以更好地阅读页面。</p> +</div> + +<h4 id="隐藏的内容">隐藏的内容</h4> + +<p>在许多情况下,视觉设计要求并非一次显示所有内容。例如,在我们的<a href="http://mdn.github.io/learning-area/css/css-layout/practical-positioning-examples/info-box.html">Tabbed info box example</a>“示例(查看<a href="https://github.com/mdn/learning-area/blob/master/css/css-layout/practical-positioning-examples/info-box.html">源码</a>)中,我们有三个信息面板,但是我们将它们放在彼此的顶部,用户可以通过单击以显示每个选项卡(也可以通过键盘访问 — 可以使用Tab键和回车键选择它们)。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/13368/tabbed-info-box.png" style="display: block; height: 400px; margin: 0px auto; width: 450px;"></p> + +<p>屏幕阅读器用户根本不关心这些,只要源内容顺序有意义,他们就很满意,并且他们可以全部获取。绝对定位(在本示例中使用的定位)通常被视为隐藏内容以产生视觉效果的最佳机制之一,因为它不会阻止屏幕阅读器获取相关内容。</p> + +<p>另一方面,你不应该使用{{cssxref("visibility")}}<code>:hidden</code>或者{{cssxref("display")}}<code>:none</code>, 因为它们会让屏幕阅读器取不到那些内容,除非你真的想让屏幕阅读器不读取那些内容。</p> + +<div class="note"> +<p><strong>Note</strong>: <span class="subtitle"><a href="http://webaim.org/techniques/css/invisiblecontent/">Invisible Content Just for Screen Reader Users</a>有更多关于这个话题的详细信息</span></p> +</div> + +<h3 id="JavaScript">JavaScript</h3> + +<p>就可访问性而言,JavaScript具有与CSS相同的问题 — 如果使用不当或使用过度,可访问性可能会很糟糕。我们已经提到了与JavaScript相关的一些可访问性问题,主要是在HTML语义化那块 — 你应该始终使用适当的语义化HTML来在合适的地方实现功能,例如,适当地使用链接和按钮。尽量不要使用JavaScript代码中结合<code><div></code>元素来伪造功能 — 容易出错,并且比直接使用HTML标签还要做更多的工作。</p> + +<h4 id="简单的功能">简单的功能</h4> + +<p>通常,简单的功能只应使用HTML标签完成 — JavaScript仅用于增强功能,而不能用于实现简单功能。好的JavaScript用法包括:</p> + +<ul> + <li>提供客户端表单验证,可在不等待服务器检查数据的情况下,迅速向用户发出有关表单条目问题的警报。如果不能在客户端验证,则该表格仍然可以使用,但验证速度可能会较慢。</li> + <li>提供全键盘用户可访问的HTML5 <code><video></code>的自定义控件(如我们之前所述,默认浏览器视频控件在大多数浏览器中均无法通过键盘访问)。</li> +</ul> + +<div class="note"> +<p><strong>Note</strong>: WebAIM的<a href="http://webaim.org/techniques/javascript/">Accessible JavaScript</a>提供了一些关于JavaScript可访问性注意事项的信息。</p> +</div> + +<p>更复杂的JavaScript实现可能会带来可访问性问题 — 你需要尽力而为。例如,期望让使用WebGL编写的复杂3D游戏对盲人来说100%可访问性是不合理的,但是你可以实现键盘控件,以便非鼠标用户可以使用它,并使配色方案具有足够的对比度供有颜色分辨障碍的人使用。</p> + +<h4 id="复杂的功能">复杂的功能</h4> + +<p>可访问性存在问题的主要领域之一是复杂的应用程序,其中涉及复杂的表单控件(例如日期选择器)和动态内容,内容会经常增量更新。</p> + +<p>非自带的复杂的表单控件可能会存在问题,因为它们往往涉及大量嵌套的<code><div></code>,浏览器默认情况下不知道如何处理它们。如果你自己开发控件,则需要确保它们可以通过键盘访问。如果你使用的是某种第三方框架,请在开始使用之前仔细检查可用的选项以了解它们是否具有完备的可访问性。例如,Bootstrap就对可访问性有相当好的支持(尽管Rhiana Heath的<span class="l-d-i l-pa2 t-bg-white"><a href="https://www.sitepoint.com/making-bootstrap-accessible/">Making Bootstrap a Little More Accessible</a></span>探讨了它的一些问题(主要与色彩对比度有关),并着眼于一些解决方案)。</p> + +<p>定期更新的动态内容可能会成为问题,因为屏幕阅读器用户可能会错过这些内容,尤其是在意外更新的情况下。如果你有一个包含主要内容面板的单页应用程序,该应用程序使用<a href="/zh-CN/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a>或<a href="/zh-CN/docs/Web/API/Fetch_API">Fetch</a>定期更新,那么屏幕阅读器用户可能会错过这些更新。</p> + +<h4 id="WAI-ARIA">WAI-ARIA</h4> + +<p>你是否需要使用这种复杂的功能,或者使用普通的旧语义化HTML代替?如果确实需要复杂性,则应考虑使用<a href="https://www.w3.org/TR/wai-aria-1.1/">WAI-ARIA</a>(Accessible Rich Internet Applications - 可访问的互联网应用),该规范为诸如复杂的表单控件和更新面板之类的项目提供了语义(以新的HTML属性形式),这样大部分浏览器和屏幕阅读器就能理解内容。</p> + +<p>要处理复杂的表单窗口小部件,你需要使用ARIA属性(例如<code>roles</code>)来声明窗口小部件中不同元素的角色(例如,它们是选项卡还是选项卡面板?),用<code>aria-disabled</code>来表示控件是否禁用等。</p> + +<p>要处理内容定期更新的区域,可以使用<code>aria-live</code>属性,该属性标识更新区域。它的值指示屏幕阅读器如何处理更新内容:</p> + +<ul> + <li><code>off:</code> 默认值。更新内容不被读出。</li> + <li><code>polite</code>: 当用户空闲时读出更新内容。</li> + <li><code>assertive</code>: 尽快读出更新内容。</li> + <li><code>rude</code>: 直接读出更新内容,即使会打断用户正常阅读。</li> +</ul> + +<p>例子如下:</p> + +<pre class="brush: html"><p><span id="LiveRegion1" aria-live="polite" aria-atomic="false"></span></p></pre> + +<p>浏览Freedom Scientific的<a href="http://www.freedomscientific.com/Training/Surfs-up/AriaLiveRegions.htm">ARIA (Accessible Rich Internet Applications)动态更新区域</a>示例 — 高亮显示的段落每10秒更新一次内容,屏幕阅读器应将此内容读出给用户。<a href="http://www.freedomscientific.com/Training/Surfs-up/AriaLiveRegionsAtomic.htm">ARIA Live Regions - Atomic</a>是另一个很有用的例子。</p> + +<p>我们这里没有足够的空间来详细介绍WAI-ARIA,你可以在<a href="/zh-CN/docs/Learn/Accessibility/WAI-ARIA_basics">WAI-ARIA basics</a>了解更多。</p> + +<h2 id="可访问性测试工具">可访问性测试工具</h2> + +<p>现在,我们已经介绍了不同Web技术的可访问性注意事项,包括一些测试方法(例如键盘导航和颜色对比度检查器),让我们看一下在进行可访问性测试时可以使用的其他工具。</p> + +<h3 id="审计工具">审计工具</h3> + +<p>你可以使用许多审计工具检查你的网页,这些审计工具将检查它们并返回页面上存在的可访问性问题列表。一些审计工具:</p> + +<ul> + <li><a href="https://tenon.io">Tenon</a>: 一个相当不错的在线应用程序,它通过提供的URL遍历代码,并返回有关可访问性错误的结果,包括度量标准,特定错误以及它们影响的WCAG标准以及建议的修复方式。</li> + <li><a href="http://khan.github.io/tota11y/">tota11y</a>: Khan Academy的可访问性工具,采用JavaScript库的形式,你可以将其附加到页面上,以提供许多可访问性工具。</li> + <li><a href="http://wave.webaim.org/">Wave</a>: 另一个在线可访问性测试工具,它接受网址并返回该页面带注释的视图,其中高亮显示了可访问性问题。</li> +</ul> + +<p>看下面的例子,我们用的是Tenon。</p> + +<ol> + <li>访问<a href="https://tenon.io">Tenon主页</a>。</li> + <li>使用<a href="http://mdn.github.io/learning-area/accessibility/html/bad-semantics.html">bad-semantics.html</a>示例测试,输入链接地址并按下<em>Analyse Your Webpage(译者注:开始分析你的网页)。</em></li> + <li>下滑,直到你看到错误/描述部分,如下图。</li> +</ol> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14217/tenon-screenshot.png" style="border-style: solid; border-width: 1px; display: block; height: 593px; margin: 0px auto; width: 870px;"></p> + +<p>你还可以探索一些选项(请参阅页面顶部附近的<em>Show Options(译者注:显示选项)</em>链接),或者使用Tenon的API。</p> + +<div class="note"> +<p><strong>Note</strong>: 这些工具不足以单独解决你的所有可访问性问题。你需要将这些,知识和经验,用户测试等结合起来才能获得完整的解决方案。</p> +</div> + +<h3 id="自动化工具">自动化工具</h3> + +<p><a href="https://www.deque.com/products/axe/">Deque's aXe tool</a>比我们上面提到的审计工具更优秀。和其他工具一样,它检查页面并返回可访问性错误。它很有用,可以提供浏览器扩展程序:</p> + +<ul> + <li><a href="http://bitly.com/aXe-Chrome">aXe for Chrome</a></li> + <li><a href="http://bit.ly/aXe-Firefox">aXe for Firefox</a></li> +</ul> + +<p>扩展程序将可访问性选项卡添加到浏览器开发人员工具。例如,我们安装了Firefox版本,然后使用它来审核<a href="http://mdn.github.io/learning-area/accessibility/html/bad-table.html">bad-table.html</a>示例。我们得到以下结果:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14213/aXe-screenshot.png" style="display: block; height: 580px; margin: 0px auto; width: 800px;"></p> + +<p>aXe也可以使用<code>npm</code>安装,并且可以与任务运行器(如<a href="http://gruntjs.com/">Grunt</a> 和<a href="http://gulpjs.com/">Gulp</a>),自动化框架(如<a href="http://www.seleniumhq.org/">Selenium</a>和<a href="https://cucumber.io/">Cucumber</a>),单元测试框架(如<a href="http://jasmine.github.io/">Jasmin</a>)集成,以及更多其他功能(详见<a href="https://www.deque.com/products/axe/">main aXe page</a> )。</p> + +<h3 id="屏幕阅读器">屏幕阅读器</h3> + +<p>为了了解有严重视力障碍的人是如何浏览网页的,我们需要测试屏幕阅读器。有几款屏幕阅读器:</p> + +<ul> + <li>有些是付费产品,比如<a href="http://www.freedomscientific.com/Products/Blindness/JAWS">JAWS</a> (Windows) 和<a href="http://www.gwmicro.com/window-eyes/">Window Eyes</a> (Windows).</li> + <li>有些是免费产品,比如<a href="http://www.nvaccess.org/">NVDA</a> (Windows), <a href="http://www.chromevox.com/">ChromeVox</a> (Chrome, Windows, and Mac OS X), 和<a href="https://wiki.gnome.org/Projects/Orca">Orca</a> (Linux).</li> + <li>有些内置在操作系统中,比如<a href="http://www.apple.com/accessibility/osx/voiceover/">VoiceOver</a> (Mac OS X and iOS), <a href="http://www.chromevox.com/">ChromeVox</a> (on Chromebooks), 和<a href="https://play.google.com/store/apps/details?id=com.google.android.marvin.talkback">TalkBack</a> (Android).</li> +</ul> + +<p>通常,屏幕阅读器是独立运行的应用程序,并且不仅仅支持阅读网页,也支持阅读其他应用程序。也有例外(比如ChromeVox是一个浏览器扩展程序)。不同的屏幕阅读器可能在控制键和表现上稍有不同,所以你必须查阅你选择的屏幕阅读器的文档来获取相关细节。总体来说他们是大同小异的。</p> + +<p>一起看一下我们对几款不同的屏幕阅读器的测试。这将帮助你大致了解它们如何工作以及如何测试它们。</p> + +<div class="note"> +<p><strong>Note</strong>: WebAIM的 <a href="http://webaim.org/techniques/screenreader/">Designing for Screen Reader Compatibility</a>提供了一些关于屏幕阅读器的使用和如何开发应用以最好的适用屏幕阅读器的信息。你也可以看下<a href="http://webaim.org/projects/screenreadersurvey6/#used">Screen Reader User Survey #6 Results</a>这篇有关屏幕阅读器一些有趣的统计信息的文章。</p> +</div> + +<h4 id="VoiceOver">VoiceOver</h4> + +<p>VoiceOver (VO)是Mac/iPhone/iPad上的免费应用,所以如果你使用苹果公司的产品,可以用VO来进行测试。 我们在Mac OS X 系统上测试了它。</p> + +<p>按下Cmd + Fn + F5打开它。如果你之前没用过VO,将会出现一个可以选择是否开启VO的欢迎界面,并且还会有教程指导你如何使用。再次按下Cmd + Fn + F5可以关闭。</p> + +<div class="note"> +<p><strong>Note</strong>: 你应该至少看一遍教程,它对于你了解VO是非常有用的。</p> +</div> + +<p>当VO开启时,你会看到一个会显示当前选中信息的黑色框在屏幕的左下角,除此之外屏幕显示大体还是相同的。当前选中的部分也会出现一个黑色的边框以进行高亮显示,这个黑色框就是VO的光标。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14219/voiceover.png" style="border-style: solid; border-width: 1px; display: block; height: 386px; margin: 0px auto; width: 800px;"></p> + +<p>在使用中,你会用到"VO修饰键","VO修饰键"是一个单独键或组合键,当你使用VO的快捷键时,你需要额外按下这个"VO修饰键"。屏幕阅读器通常都会有修饰键,防止它们的快捷键和其他程序的快捷键冲突。VO的修饰键是CapsLock, 或Ctrl + Option。</p> + +<p>VO有很多快捷键,我们没有全部列出来。只把测试网页可访问性常用的一些在下面列出了。表格里,"VO"代表"VO修饰键"。</p> + +<table class="standard-table"> + <caption>常用的VO快捷键</caption> + <thead> + <tr> + <th scope="col">快捷键</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td>VO + 方向键</td> + <td>移动VO光标</td> + </tr> + <tr> + <td>VO + 空格键</td> + <td>选择/激活高亮的部分,包括Rotor里的内容(关于Rotor见下面)</td> + </tr> + <tr> + <td>VO + Shift + 下</td> + <td>移动到组合项目里(比如HTML表格,或表单等)。进入组合里,你能使用下面的快捷键。</td> + </tr> + <tr> + <td>VO + Shift + 上</td> + <td>离开组合项目</td> + </tr> + <tr> + <td>VO + C</td> + <td>(当在表格里面时) 阅读当前列的头部</td> + </tr> + <tr> + <td>VO + R</td> + <td>(当在表格里面时) 阅读当前行的头部</td> + </tr> + <tr> + <td>VO + C + C </td> + <td>(当在表格里面时) 阅读当前列,包括列头</td> + </tr> + <tr> + <td>VO + R + R </td> + <td>(当在表格里面时) 阅读当前行,包括对应于每个小行的头</td> + </tr> + <tr> + <td>VO + 左, VO + 右</td> + <td>(当在水平的选项卡里面时,比如日期选择或时间选择) 切换选项</td> + </tr> + <tr> + <td>VO + 上, VO + 下</td> + <td>(当在垂直的选项卡里面时,比如日期选择或时间选择) 切换选项</td> + </tr> + <tr> + <td>VO + U</td> + <td>使用Rotor。Rotor使用列表展示标题,链接,表单选项等,以便为我们提供便利的导航。</td> + </tr> + <tr> + <td>VO + 左, VO + 右</td> + <td>(当在Rotor里) 切换到其他列表</td> + </tr> + <tr> + <td>VO + 上, VO + 下</td> + <td>(当在Rotor里) 在当前列表里,切换到其他项</td> + </tr> + <tr> + <td>Esc</td> + <td>(当在Rotor里) 推出Rotor</td> + </tr> + <tr> + <td>Ctrl</td> + <td>(当VO阅读时) 暂停/继续</td> + </tr> + <tr> + <td>VO + Z</td> + <td>重复上一句话</td> + </tr> + <tr> + <td>VO + D</td> + <td>进入Mac的程序坞,你能选择运行哪个应用</td> + </tr> + </tbody> +</table> + +<p>看起来很多,但当你用起来时还好,因为VO通常会给你提醒,在哪里应该用哪个快捷键。现在试试运行VO,在{{anch("屏幕阅读器测试")}}屏幕阅读器测试章节做个测试吧。</p> + +<h4 id="NVDA">NVDA</h4> + +<p>NVDA只能运行在Window系统,你需要安装它。</p> + +<ol> + <li>在<a href="http://www.nvaccess.org/">nvaccess.org</a>下载。你能选择免费下载,或赞助后再下载;你需要在下载前提供你的邮箱地址。</li> + <li>下载完成后,开始安装 - 双击安装程序,接受条款,一步步按提示来。</li> + <li>双击NVDA程序或快捷方式,或者按下Ctrl + Alt + N打开它。你会看见欢迎界面。你能选择一些选项,然后按下OK继续。</li> +</ol> + +<p>NVDA现在在你的电脑上开启了。</p> + +<p>在使用中,你会用到"NVDA修饰键","NVDA修饰键"是一个单独键,当你使用NVDA的快捷键时,你需要额外按下这个"NVDA修饰键"。屏幕阅读器通常都会有修饰键,防止它们的快捷键和其他程序的快捷键冲突。NVDA的修饰键是Insert键(默认), 或CapsLock键(在欢迎界面可以选择使用该键)。</p> + +<div class="note"> +<p><strong>Note</strong>: 关于高亮方面,NVDA比VoiceOver做的更好。当滚动过标题、列表等元素时,你选中的项目会被一个细微的边框包住以高亮,但不是对于所有元素都是这样的。如果你感觉迷失方向了,可以按Ctrl + F5刷新页面,并从顶部重新开始。</p> +</div> + +<p>NVDA有很多快捷键,我们没有全部列出来。只把测试网页可访问性常用的一些在下面列出了。表格里,"NVDA"代表"NVDA修饰键"。</p> + +<table class="standard-table"> + <caption>常用的NVDA快捷键</caption> + <thead> + <tr> + <th scope="col">快捷键</th> + <th scope="col">描述</th> + </tr> + </thead> + <tbody> + <tr> + <td>NVDA + Q</td> + <td>关闭NVDA</td> + </tr> + <tr> + <td>NVDA + 上</td> + <td>阅读当前行</td> + </tr> + <tr> + <td>NVDA + 下</td> + <td>在当前位置开始阅读</td> + </tr> + <tr> + <td>上和下, 或者Shift + Tab 和Tab</td> + <td>移动到上/下一项,开始阅读</td> + </tr> + <tr> + <td>左和右</td> + <td>移动到当前项的上/下一个字符,开始阅读</td> + </tr> + <tr> + <td>Shift + H 和 H</td> + <td>移动到上/下一标题,开始阅读</td> + </tr> + <tr> + <td>Shift + K 和 K</td> + <td>移动到上/下一链接项,开始阅读</td> + </tr> + <tr> + <td>Shift + D 和 D</td> + <td>移动到上/下一文档界标(比如<nav>),开始阅读</td> + </tr> + <tr> + <td>Shift + 1–6 和 1–6</td> + <td>移动到上/下一标题(标题1 - 6),开始阅读</td> + </tr> + <tr> + <td>Shift + F 和 F</td> + <td>移动到上/下一表单选项,聚焦</td> + </tr> + <tr> + <td>Shift + T 和 T</td> + <td>移动到上/下一数据表,聚焦</td> + </tr> + <tr> + <td>Shift + B 和 B</td> + <td>移动到上/下一按钮,阅读它的label</td> + </tr> + <tr> + <td>Shift + L 和 L</td> + <td>移动到上/下一列表,阅读它的第一项</td> + </tr> + <tr> + <td>Shift + I 和 I</td> + <td>移动到上/下一列表,开始阅读</td> + </tr> + <tr> + <td>Enter/Return</td> + <td>(当链接或按钮或其他可激活的项选中时) 激活项</td> + </tr> + <tr> + <td>NVDA + 空格</td> + <td>(当选中表单时) 进入表单,或如果已经在表单里的情况下,离开表单</td> + </tr> + <tr> + <td>Shift Tab 和 Tab</td> + <td>(当在表单里面时) 切换到下一个input</td> + </tr> + <tr> + <td>上 和 下</td> + <td>(当在表单里面时) 改变input的值(例如选择框).</td> + </tr> + <tr> + <td>空格</td> + <td>(当在表单里面时) 选择已选中的值</td> + </tr> + <tr> + <td>Ctrl + Alt + 方向键</td> + <td>(当选中表格时) 切换表格单元格</td> + </tr> + </tbody> +</table> + +<h4 id="屏幕阅读器测试">屏幕阅读器测试</h4> + +<p>现在你学会了如何使用屏幕阅读器,来使用它做一些可访问性测试吧。这样你才能了解屏幕阅读器在好的网页和坏的网页之间不同的表现:</p> + +<ul> + <li>浏览<a href="http://mdn.github.io/learning-area/accessibility/html/good-semantics.html">good-semantics.html</a>, 留意屏幕阅读器如何找到标题并将其用于导航。再看一下<a href="http://mdn.github.io/learning-area/accessibility/html/bad-semantics.html">bad-semantics.html</a>, 留意屏幕阅读器不会得到这些信息。想象一下,当尝试浏览非常长的文本页面时,这会是多么使人烦躁。</li> + <li>浏览<a href="http://mdn.github.io/learning-area/accessibility/html/good-links.html">good-links.html</a>, 并留意当不在上下文时他们对于你理解内容的帮助。而<a href="http://mdn.github.io/learning-area/accessibility/html/bad-links.html">bad-links.html</a> 就无法帮助你理解— 他们仅仅会提示"click here(点击这里)".</li> + <li>浏览<a href="http://mdn.github.io/learning-area/accessibility/html/good-form.html">good-form.html</a>, 并注意如何使用其标签描述表单输入,因为我们已经正确使用了<label>。在<a href="http://mdn.github.io/learning-area/accessibility/html/bad-form.html">bad-form.html</a>, 只能获取到无用的空白标签。</li> + <li>浏览<a href="http://mdn.github.io/learning-area/css/styling-boxes/styling-tables/punk-bands-complete.html">punk-bands-complete.html</a>, 并查看屏幕阅读器如何将内容的列和行关联起来,并一起读取它们,因为我们已经正确定义了标头。在<a href="http://mdn.github.io/learning-area/accessibility/html/bad-table.html">bad-table.html</a>, 无法得知单元格的关联关系。请注意,当页面上只有一个表时,NVDA似乎表现得有些杂乱无章。你可以改用<a href="http://webaim.org/articles/nvda/tables.htm">WebAIM's table test page</a>进行测试。</li> + <li>看一下之前看过的<a href="http://www.freedomscientific.com/Training/Surfs-up/AriaLiveRegions.htm">WAI-ARIA live regions example</a>, 并注意屏幕阅读器将实时读取不断更新的部分。</li> +</ul> + +<h3 id="用户测试">用户测试</h3> + +<p>如上所述,你不能仅依靠自动化工具来确定网站上的可访问性问题。建议在制定测试计划时,包含一些用户测试可访问性的计划(有关更多内容,请参阅前面的<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies#User_testing">用户测试</a>部分)。尝试让一些屏幕阅读器用户,一些全键盘用户,一些有听觉障碍的用户或其他用户参与测试,以满足你的需求。</p> + +<h2 id="测试可访问性检查清单">测试可访问性检查清单</h2> + +<p>以下列表提供了一个清单,供参考,以确保已对项目执行建议的可访问性测试:</p> + +<ol> + <li>确保HTML尽可能是语义化的。<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#%E9%AA%8C%E8%AF%81">验证</a>是一个好方法, 就像使用<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E5%AE%A1%E8%AE%A1%E5%B7%A5%E5%85%B7">审计工具</a>。</li> + <li>检查当关闭CSS时,你的内容能够被理解。</li> + <li>确认功能是<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E4%BD%BF%E7%94%A8%E9%94%AE%E7%9B%98">全键盘可访问</a>的。使用Tab键、回车键等做测试。</li> + <li>确保非文本内容有<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E6%9B%BF%E4%BB%A3%E6%96%87%E6%9C%AC">替代文本</a>。 <a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E5%AE%A1%E8%AE%A1%E5%B7%A5%E5%85%B7">审计工具</a>能够很好地发现问题。</li> + <li>确保<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E9%A2%9C%E8%89%B2%E5%92%8C%E9%A2%9C%E8%89%B2%E5%AF%B9%E6%AF%94%E5%BA%A6">颜色和颜色对比度</a>是可接受的,使用合适的工具测试。</li> + <li>确保<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E9%9A%90%E8%97%8F%E7%9A%84%E5%86%85%E5%AE%B9">隐藏的内容</a>可以被屏幕阅读器读取。</li> + <li>尽可能的使功能在没有JavaScript的情况下也可以正常使用。</li> + <li>在合适的地方使用ARIA来提供可访问性。</li> + <li>使用<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/%E5%8F%AF%E8%AE%BF%E9%97%AE%E6%80%A7#%E5%AE%A1%E8%AE%A1%E5%B7%A5%E5%85%B7">审计工具</a>测试一下你的网站。</li> + <li>使用屏幕阅读器实际测试一下。</li> + <li>在你的网站上可以添加可访问性策略/声明,以说明你的为可访问性做了什么。</li> +</ol> + +<h2 id="寻找帮助">寻找帮助</h2> + +<p>可访问性还会遇到许多其他问题。你要学会如何在线查找答案。请查阅HTML和CSS文章的“<a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS#%E5%AF%BB%E6%89%BE%E5%B8%AE%E5%8A%A9">寻找帮助</a>”部分,以获取一些指导。 </p> + +<h2 id="总结">总结</h2> + +<p>希望本文能让你了解可访问性,以及帮助你解决遇到的一些可访问性问题。</p> + +<p>下一篇文章,我们将详细介绍特征检测。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/JavaScript","Learn/Tools_and_testing/Cross_browser_testing/Feature_detection", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + +<h2 id="指南">指南</h2> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">测试策略</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">处理常见的HTML和CSS问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的可访问性问题</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">实现特征检查</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动测试简介</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">建立你自己的自动化测试环境</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/cross_browser_testing/测试策略/index.html b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/测试策略/index.html new file mode 100644 index 0000000000..fb01cd2843 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/cross_browser_testing/测试策略/index.html @@ -0,0 +1,367 @@ +--- +title: 进行测试的策略 +slug: Learn/Tools_and_testing/Cross_browser_testing/测试策略 +tags: + - 测试 + - 测试策略 + - 用户测试 + - 自动化测试 + - 虚拟机 仿真器 + - 跨浏览器测试 +translation_of: Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Introduction","Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS", "Learn/Tools_and_testing/Cross_browser_testing")}}</div> + +<p class="summary"><br> + 这篇文章主要是讨论如何进行跨浏览器测试,回答一些比较常见的疑惑,譬如:“什么是跨浏览器测试?”,“会遇到哪些常见的问题?”以及“如何测试、区分以及修复问题?”</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">准备:</th> + <td>熟练掌握 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言; 了解<a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试的核心概念</a>。</td> + </tr> + <tr> + <th scope="row">对象:</th> + <td>了解跨浏览器测试所涉及的高级概念。</td> + </tr> + </tbody> +</table> + +<h2 id="是否需要测试?">是否需要测试?</h2> + +<p>开始跨浏览器测试(cross browser testing)之前您需要确定哪些浏览器将进入被测试名单。测试所有用户可能使用的浏览器或移动设备是不可能的——数量太大,而且浏览器和设备总在不断更新。</p> + +<p>可行的替代方案是,尝试确保您的网站适用于最常见的浏览器和设备,然后进行防御性编码,以便为您的网站提供最广泛的支持范围。</p> + +<p>防御性编码的实质是一种智能回退措施:如果某个功能或样式在浏览器中不起作用,该网站将将降级为不太令人兴奋的东西,但仍然能提供可接受的用户体验——核心是:虽然网站看起来不那么漂亮,仍然可以访问。</p> + +<p>本章的目的是建立一个供您在测试时参考的浏览器/设备图表。 您可以根据需要将其设置成简单或复杂版本——常见的方法是具有多个等级的支持,例如:</p> + +<ol> + <li>A 级:普通/现代浏览器(仍在广泛使用):需要彻底测试并提供全面支持。</li> + <li>B 级:较旧/较少的浏览器 (少数用户还在使用):测试并提供更基本的体验,以提供对核心信息和服务的完全访问。</li> + <li>C 级:稀有/未知浏览器 : 不进行测试,假设网站内容可以显示。 在我们的防御性编码起效的情况下,用户可以访问到网页的全部内容。</li> +</ol> + +<p>在以下各节中,我们将以此等级为参考构建支持图表。</p> + +<div class="note"> +<p><strong>注意:雅虎最先使这一方法流行起来,详情参见《浏览器分级支持系统》(</strong> <a href="https://github.com/yui/yui3/wiki/Graded-Browser-Support">Graded browser Support</a><strong>)。</strong></p> +</div> + +<h3 id="有根据地假设">有根据地假设</h3> + +<p>您可以称之为“假设”或“直觉”。这不是一种准确,科学的方法,但作为具有网络行业经验的人,您至少应该对待测试的一些浏览器有些想法。这将形成一个良好的初级浏览器分级支持系统。</p> + +<p>例如,如果您居住在西欧或北美,您会发现许多人使用 Windows 和 Mac 的台式机或笔记本电脑,主要浏览器是 Chrome,Firefox,Safari,IE 和 Edge。您可以测试前三个浏览器最近的三个版本,因为它们会定期更新。对于Edge和IE,您应当近期的多个版本。这些浏览器都应该属于A级。</p> + +<div class="note"> +<p><strong>注意:</strong>您一次只能在计算机上安装一个版本的 IE 或 Edge,因此您可能必须使用虚拟机或其他方法来执行所需的测试。稍后请参阅虚拟机部分{{anch("Virtual machines")}}。</p> +</div> + +<p>很多人使用 iOS 和 Android,因此您可能还想测试最新版本的 iOS Safari,最近几个版本的 Android stock 浏览器,以及适用于 iOS 和 Android 的 Chrome 和 Firefox。理想情况下,您应该在手机和平板电脑上测试这些,以确保响应式设计的正常运行。</p> + +<p>您也许知道很多人仍然在使用 IE 9。这一版本陈旧而且功能较少,所以让我们把它放在B级别。</p> + +<p>到目前为止,这为我们提供了以下支持级别:</p> + +<ol> + <li>A 级:用于 Windows / Mac 的 Chrome 和 Firefox,用于 Mac 的 Safari,用于 Windows的 Edge 和 IE(每个版本的最后两个版本),用于 iPhone / iPad 的 iOS Safari,用于手机/平板电脑的 Android stock 浏览器(最后两个版本),用于手机和平板电脑上的 Chrome 和适用于 Android 的Firefox(最后两个版本)。</li> + <li>B 级:适用于 Windows 的 IE 9</li> + <li>C 级:无</li> +</ol> + +<p>如果您居住在其他地方,或者正在开发其他地方(例如某些国家或地区)的网站,那么您可能会有不同的常用浏览器进行测试。</p> + +<div class="note"> +<p><strong>注意:</strong>“我老板用Blackberry,所以我们最好确保它看起来很好”也可以是一个有说服力的论点。</p> +</div> + +<h3 id="浏览器支持检测">浏览器支持检测</h3> + +<p>您可以调用的一个有用的措施是浏览器支持统计信息,用于告知您的浏览器测试选项。有许多网站提供此类统计信息,例如:</p> + +<ul> + <li><a href="https://www.netmarketshare.com/browser-market-share.aspx?qprid=2&qpcustomd=0">Netmarketshare</a></li> + <li><a href="http://gs.statcounter.com/">Statcounter</a></li> +</ul> + +<p>这些都以北美为中心,并不特别准确,但它们可以让您了解大趋势。</p> + +<p>例如,让我们转到 <a href="https://www.netmarketshare.com/browser-market-share.aspx?qprid=2&qpcustomd=0">Netmarketshare</a>。您可以看到 Opera 被列为具有小但可见的使用数字,因此,我们也应该将其添加到支持图表中,作为 C 级。</p> + +<p>IE8 被列为重要,但它较老且不再能够。Opera Mini 非常重要,但在运行时运行复杂的 JavaScript 方面,它的能力并不大(有关详细信息,请参阅 <a href="https://dev.opera.com/articles/opera-mini-and-javascript/">Opera Mini and JavaScrip</a>)。我们也应该把它放到B级。</p> + +<h3 id="使用分析工具">使用分析工具</h3> + +<p>更准确的数据源,如果你能得到它,来自像<a href="https://www.google.com/analytics/">Google Analytics</a>这样的分析应用程序。这是一个应用程序,将为您提供准确的统计数据,究竟是什么浏览器的人正在使用浏览您的网站。当然,这依赖于您已经有一个网站使用它,所以它是没有多大的适合全新的网站。</p> + +<p>但是,分析历史记录可用于查找支持统计信息以影响公司网站的新版本或要添加到现有站点的新功能。如果您有这些可用,它们比上述全球浏览器统计信息更准确。</p> + +<h4 id="配置Google分析">配置Google分析</h4> + +<ol> + <li>首先,您需要一个谷歌帐户。使用此帐户可登录<a href="https://www.google.com/analytics/">Google Analytics</a>。 </li> + <li>选择 <a href="https://analytics.google.com/analytics/web/">Google Analytics</a> (web))选项,然后单击"注册"按钮。</li> + <li>在注册页面中输入您的网站/应用详细信息。这是相当直观的设置;获得正确的最重要的字段是网站 URL。这需要是您的网站/应用的根 URL。</li> + <li>填写完所有内容后,按"获取跟踪 ID"按钮,然后接受显示的服务条款。</li> + <li>下一页为您提供一些代码段和其他说明。对于基本网站,您需要做的是复制网站跟踪代码块,并将其粘贴到您要在网站上使用 Google Analytics 跟踪的所有不同页面。您可以在关闭<code></body></code>标记下方,也可以位于其他适当位置,以防止其与应用程序代码混淆。</li> + <li>将更改上载到开发服务器,或将代码上载到其他任何位置。</li> +</ol> + +<p>就是这样!您的网站现在应该准备好开始报告分析数据。</p> + +<h4 id="学习分析数据">学习分析数据</h4> + +<p>现在,您应该能够返回 <a href="https://analytics.google.com/analytics/web">Analytics Web</a>主页,并开始查看您收集的有关网站的数据(当然,您需要留出一点时间才能真正收集一些数据)。</p> + +<p>默认情况下,您应该看到报告选项卡,如下所示:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14081/analytics-reporting.png" style="border-style: solid; border-width: 1px; display: block; height: 386px; margin: 0px auto; width: 700px;"></p> + +<p>有大量的数据,你可以看看使用谷歌分析––自定义报告在不同的类别,等等––我们没有时间讨论这一切。 <a href="https://support.google.com/analytics/answer/1008015">Getting started with Analytics</a> 为初学者提供了一些有关报告(以及更多)的有用指导。</p> + +<p>还应鼓励您查看左侧的不同选项,并查看您可以找到哪些类型的数据。例如,您可以通过从左侧菜单中选择" <em>Audience > Technology > Browser & OS</em>"来了解用户正在使用的浏览器和操作系统。</p> + +<div class="note"> +<p><strong>注意</strong>:使用 Google 分析时,您需要提防误导性偏见,例如"我们没有 Firefox 移动用户"可能会导致您不必为 Firefox 移动提供支持。但是,你不会有任何火狐移动用户,如果该网站被打破在火狐手机摆在首位。</p> +</div> + +<h3 id="其他注意事项">其他注意事项</h3> + +<p>您可能还应包括其他注意事项。您绝对应该将辅助功能作为 A 级测试要求(我们将在处理常见辅助功能问题一文中介绍您应该测试的内容)</p> + +<p>此外,您可能还有其他注意事项。如果要创建某种公司 Intranet,以便向经理提供销售数据,并且例如,所有经理都提供了 Windows 手机,则可能需要将移动 IE 支持作为优先事项。</p> + +<h3 id="最终的支持图表">最终的支持图表</h3> + +<p>因此,我们的最终支持图表最终将如下所示:</p> + +<ol> + <li>A 级:适用于 Windows/Mac 的 Chrome 和 Firefox、适用于 Mac 的 Safari、Windows 的"边缘"和"IE"(每个版本的最后两个版本)、iPhone/iPad 的 iOS Safari、手机/平板电脑上的 Android 股票浏览器(最后两个版本)、适用于 Android 的 Chrome 和 Firefox(最后两个版本)在手机平板电脑上.通过常见测试的可访问性。</li> + <li>B 级:IE 8 和 9 用于 Windows,Opera Mini。</li> + <li>C级:Opera,其他合适的现代浏览器。</li> +</ol> + +<h2 id="你想要测试什么?">你想要测试什么?</h2> + +<p>当您的代码库有需要测试的新添加项时,在开始测试之前,应编写出需要通过才能接受的测试要求列表。这些要求可以是可视的,也可以是功能性的, 两者结合起来, 成为可用的网站功能。</p> + +<p>思考下面的例子 (查看<a href="https://github.com/mdn/learning-area/blob/master/tools-testing/cross-browser-testing/strategies/hidden-info-panel.html">源码 </a>, <a href="http://mdn.github.io/learning-area/tools-testing/cross-browser-testing/strategies/hidden-info-panel.html">在线预览</a>):</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14083/sliding-box-demo.png" style="border-style: solid; border-width: 1px; display: block; height: 455px; margin: 0px auto; width: 700px;"></p> + +<p>此功能的测试标准可以这样编写:</p> + +<p>A 级和 B 级:</p> + +<ul> + <li>按钮应该由用户的主要控制机制激活,不管它是什么 ––这应该包括鼠标、键盘和触摸</li> + <li>切换按钮应使信息框显示/消失。</li> + <li>文本应可读。</li> + <li>使用屏幕阅读器的视障用户应该能够访问文本。</li> +</ul> + +<p>A 级:</p> + +<ul> + <li>信息框在出现/消失时应平滑地动画。</li> + <li>渐变和文本阴影应显示为增强框的外观。</li> +</ul> + +<p>You may notice from the text in the example that it won't work in IE8 — this is a problem according to our support chart, which you'll have to work on, perhaps by using a feature detection library to implement the functionality in a different way if the browser doesn't support CSS transitions (see Implementing feature detection, later on in the course).您可能会从示例中的文本中注意到它在 IE8 中不起作用 ––根据我们的支持图表,这是一个问题,您必须处理这个问题,如果浏览器不采用其他方式,则可能使用功能检测库以不同的方式实现功能(如果浏览器不支持 CSS 转换的话)(请参阅实现功能检测,稍后将在本课程中)。</p> + +<p>您可能还注意到,该按钮仅使用键盘无法使用, 这也需要纠正。也许我们可以使用一些JavaScript来实现一个键盘控件的切换,或者完全使用一些其他的方法?</p> + +<p>这些测试条件很有用,因为:</p> + +<ul> + <li>当您执行测试时,它们会为您提供一组要遵循的步骤。</li> + <li>在执行测试时,可以轻松地将它们转换为用户组要遵循的指令集(e.g. "try to active the button using your mouse, and then the keyboard...")–– 请参阅下面的 {{anch("User testing")}}。</li> + <li>它们还可以为编写自动测试提供基础。如果您确切知道要测试的内容以及成功条件是什么(请参阅使用自动化工具自动执行浏览器测试,请稍后在系列中)编写此类测试。</li> +</ul> + +<h2 id="建立一个测试实验室">建立一个测试实验室</h2> + +<p>执行浏览器测试的一个选项是自己进行测试。为此,您可能需要使用实际物理设备和模拟环境的组合(使用仿真器或虚拟机)。</p> + +<h3 id="真实设备">真实设备</h3> + +<p>通常最好让一个实际的设备运行您要测试的浏览器 , 这在行为和整体用户体验方面提供了最高的准确性。对于合理的低级设备实验室,您可能需要以下内容:</p> + +<ul> + <li>Mac,安装了您需要测试的浏览器,包括火狐、Chrome、Opera 和 Safari。</li> + <li>安装了需要测试的浏览器的 Windows 电脑,其中可能包括Edge (or IE), Chrome, Firefox和 Opera。</li> + <li>更高规格的Android手机和平板电脑,安装了浏览器,你需要测试 - 这可能包括Chrome,火狐,和Opera Mini的Android,以及原来的Android stock浏览器。</li> + <li>更高规格的 iOS 手机和平板电脑,并安装了您需要测试的浏览器 , 这可能包括 iOS Safari,以及适用于 iOS 的 Chrome、Firefox 和 Opera Mini。</li> +</ul> + +<p>如果可以获取这些选项,则以下是不错的选项:</p> + +<ul> + <li>可用的Linux PC ,以防您需要测试特定于 Linux 版本的浏览器的错误。Linux用户只使用火狐、Opera 和 Chrome。如果只有一台计算机可用,则可以考虑在单独的分区上创建运行 Linux 和 Windows 的双引导计算机。Ubuntu 的安装程序使设置起来非常简单;有关此帮助,请参阅<a href="https://help.ubuntu.com/community/WindowsDualBoot">WindowsDualBoot</a>。</li> + <li>几个低规格的移动设备,因此您可以测试低功率处理器上的动画等功能的性能。</li> +</ul> + +<p>您的主工作计算机也可以用于安装其他工具以用于特定目的,例如辅助功能审核工具、屏幕阅读器和仿真器/虚拟机。</p> + +<p>一些大型公司拥有设备实验室,这些实验室库存了大量不同的设备,使开发人员能够在非常具体的浏览器/设备组合上查找 Bug。较小的公司和个人通常负担不起如此复杂的实验室,因此倾向于使用较小的实验室、仿真器、虚拟机和商业测试应用程序。</p> + +<p>我们将介绍下面的其他选项。</p> + +<div class="note"> +<p><strong>注意</strong>:已做出一些努力用来创建可公开访问的设备实验室, 请参阅 <a href="https://opendevicelab.com/">Open Device Labs</a>。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>:我们还需要考虑辅助功能 — 可以在计算机上安装许多有用的工具,以方便辅助功能测试,但我们将在本课程的后面部分介绍"处理常见辅助功能问题"一文中的工具。</p> +</div> + +<h3 id="模拟器">模拟器</h3> + +<p>仿真器基本上是在计算机内运行并模拟某种设备或特定设备条件的程序,允许您比查找要测试的特定硬件/软件组合更方便地执行某些测试。</p> + +<p>仿真器可能与测试设备条件一样简单。例如,如果要对宽度/高度媒体查询进行一些快速而粗劣的测试以进行响应式设计,则可以使用 Firefox 的<a href="/en-US/docs/Tools/Responsive_Design_Mode">Responsive Design Mode</a>。 Safari 也有类似的模式,可以通过访问 “<em>Safari > 首选项”</em>和"显示开发"菜单,然后选择"开发"&gt;"输入响应式设计模式"来启用。 Chrome 也有类似的功能:设备模式(请参阅<a href="https://developers.google.com/web/tools/chrome-devtools/device-mode/">Simulate Mobile Devices with Device Mode</a>)。</p> + +<p>不过,您经常必须安装某种仿真器。要测试的最常见设备/浏览器如下所示:</p> + +<ul> + <li>用于开发Android应用程序的官方<a href="https://developer.android.com/studio/">Android Studio IDE</a>对于仅测试Google Chrome或旧版Android浏览器上的网站来说有点沉重,但它确实附带了一个强大的<a href="https://developer.android.com/studio/run/emulator.html">emulator</a>。如果你想要更轻巧的东西,<a href="http://leapdroid.com/">LeapDroid</a>是Windows的一个很好的选择,<a href="http://www.andyroid.net/">Andy</a> 是一个合理的选择,可以在Windows和Mac上运行。</li> + <li>Apple提供了一个名为<a href="https://developer.apple.com/library/content/documentation/IDEs/Conceptual/iOS_Simulator_Guide/Introduction/Introduction.html">Simulator</a> 的应用程序,它运行在<a href="https://developer.apple.com/xcode/">XCode</a>开发环境之上,并模拟iPad / iPhone / Apple Watch / Apple TV。这包括本机iOS Safari浏览器。不幸的是,这只能在Mac上运行。</li> +</ul> + +<p>您也经常可以为其他移动设备环境找到模拟器,例如:</p> + +<ul> + <li><a href="https://developer.blackberry.com/develop/simulator/">Blackberry</a> (适用于Windows,Mac OSX和Linux的模拟器)。</li> + <li>如果要进行测试,可以单独模拟<a href="https://dev.opera.com/articles/installing-opera-mini-on-your-computer/">Opera Mini</a>。</li> + <li>适用于Windows Mobile操作系统的仿真器:请参阅<a href="https://msdn.microsoft.com/en-us/library/windows/apps/ff402563(v=vs.105).aspx">Windows Phone Emulator for Windows Phone 8</a> 和<a href="https://msdn.microsoft.com/en-us/windows/uwp/debug-test-perf/test-with-the-emulator">Test with the Microsoft Emulator for Windows 10 Mobile</a>(这些仅适用于Windows)。</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:许多模拟器实际上需要使用虚拟机(见下文);在这种情况下,通常提供指令,和/或将虚拟机的使用合并到仿真器的安装程序中。</p> +</div> + +<h3 id="虚拟机">虚拟机</h3> + +<p>虚拟机是在台式计算机上运行的应用程序,允许您运行整个操作系统的仿真,每个操作系统都划分在自己的虚拟硬盘驱动器中(通常由主机硬盘上存在的单个大文件表示)。有许多流行的虚拟机应用程序可用,例如<a href="www.parallels.com/">Parallels</a>,<a href="http://www.vmware.com/">VMWare</a>和<a href="https://www.virtualbox.org/wiki/Downloads">Virtual Box</a>;我们个人喜欢后者,因为它是免费的。</p> + +<div class="note"> +<p><strong>注意</strong>:您需要可用的大容量硬盘空间来运行虚拟机模拟;模拟的每个操作系统都会占用大量内存。您可能倾向于为每次安装选择所需的硬盘空间;你也可能会试图侥幸使用10GB空间,但会被建议使用50GB空间或更多,以便让操作系统运行可靠。大多数虚拟机应用程序提供的一个很好的选择是创建一个动态分配(<strong>dynamically allocated)</strong>的硬盘驱动器,它会随着需要的增长和缩小而动态改变。</p> +</div> + +<p>要使用虚拟盒,您需要:</p> + +<ol> + <li>获取要模拟的操作系统的安装程序磁盘或映像(例如ISO文件)。 Virtual Box无法提供这些;大多数,如Windows操作系统,是无法自由分发的商业产品。</li> + <li><a href="https://www.virtualbox.org/wiki/Downloads">下载</a>适用于您的操作系统的相应安装程序并进行安装。</li> + <li>打开应用程序;您将看到如下视图: <img alt="" src="https://mdn.mozillademos.org/files/14089/virtualbox.png" style="display: block; height: 512px; margin: 0px auto; width: 700px;"></li> + <li>要创建新虚拟机,请按左上角的“新建”按钮。</li> + <li>按照说明进行操作,并根据需要填写以下对话框。你会: + <ol> + <li>为新虚拟机提供名称</li> + <li>选择要在其上安装的操作系统和版本</li> + <li>设置应分配多少RAM(我们建议使用2048MB或2GB)</li> + <li>创建虚拟硬盘(在包含立即创建虚拟硬盘,VDI(虚拟磁盘映像)和动态分配的三个对话框中选择默认选项)。</li> + <li>选择虚拟硬盘的文件位置和大小(选择一个合理的名称和位置来保留它,并且大小指定大约50GB,或者您可以轻松指定)。</li> + </ol> + </li> +</ol> + +<p>现在,新的虚拟框应出现在Virtual Box UI主窗口的左侧菜单中。此时,您可以双击它以打开虚拟机––它将开始启动虚拟机,但它还没有安装操作系统。此时,您需要将对话框指向安装程序映像/磁盘,它将运行在虚拟机上安装它的步骤,就像它是真正的计算机一样。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14085/virtualbox-installer.png" style="display: block; margin: 0 auto;"></p> + +<div class="warning"> +<p><strong>重要提示</strong>:您需要确保此时要在虚拟机上安装要安装的操作系统映像,然后立即安装。如果此时取消该进程,它可能会使虚拟机无法使用,并使其成为您需要删除它并再次创建它。这不是致命的,但令人讨厌。</p> +</div> + +<p>完成此过程后,您应该有一台虚拟机在主机窗口内运行操作系统。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14087/virtualbox-running.png" style="display: block; margin: 0 auto;"></p> + +<p>您需要像处理任何实际安装一样处理此虚拟操作系统安装 - 例如,安装要测试的浏览器,安装防病毒程序以保护其免受病毒侵害。</p> + +<p>拥有多个虚拟机非常有用,特别是对于Windows IE / Edge测试 - 在Windows上,您无法并排安装多个版本的默认浏览器,因此您可能需要构建一个虚拟机库来处理根据需要进行不同测试,例如:</p> + +<ul> + <li>Windows 10 with Edge 14</li> + <li>Windows 10 with Edge 13</li> + <li>Windows 8.1 with IE11</li> + <li>Windows 8 with IE10</li> + <li>Windows 7 with IE9</li> + <li>Windows XP with IE8</li> + <li>Windows XP with IE7</li> + <li>Windows XP with IE6</li> +</ul> + +<div class="note"> +<p><strong>注意</strong>:虚拟机的另一个好处是虚拟磁盘映像是相当独立的。如果您正在团队中工作,则可以创建一个虚拟磁盘映像,然后复制并传递它。如果它是许可产品,只需确保您拥有运行所有Windows副本或正在运行的任何其他副本所需的许可证。</p> +</div> + +<h3 id="自动化和商业应用">自动化和商业应用</h3> + +<p>正如上一章所述,通过使用某种自动化系统,您可以从浏览器测试中减少很多痛苦。您可以设置自己的测试自动化系统(<a href="http://www.seleniumhq.org/">Selenium</a>是首选的流行应用程序),它确实需要一些设置,但是当您解决问题时可能会相当受益。</p> + +<p>还有一些商业工具,如<a href="https://saucelabs.com/">Sauce Labs</a>和<a href="https://www.browserstack.com/">Browser Stack</a>,可以为您做这种事情,如果您愿意在测试中投入一些资金,则无需担心设置的问题。</p> + +<p>我们将在后续文章中查看如何使用此类工具。</p> + +<h2 id="用户测试">用户测试</h2> + +<p>在我们继续之前,我们将通过谈论用户测试来完成本文––如果您有一个愿意用户组来测试您的新功能,这可能是一个很好的选择。请记住,这可以像你希望的那样廉价又有效––你的用户群可以是一群朋友,一群同事,或一群无偿或有偿志愿者,这取决于你是否有钱花在测试上。</p> + +<p>通常,您可以让用户在某种开发服务器上查看包含新功能的页面或视图,这样您就不会在完成之前放置最终站点或更改。你应该让他们按照一些步骤报告他们得到的结果。提供一组步骤(有时称为脚本)非常有用,这样您就可以获得与您尝试测试的内容相关的更可靠的结果。我们在上面的{{anch("What are you going to test")}} 部分中提到了这一点––很容易将其中详述的测试标准转换为要遵循的步骤。例如,以下内容适用于有视力的用户:</p> + +<ul> + <li>在台式计算机上使用鼠标单击问号按钮几次。刷新浏览器窗口。</li> + <li>使用台式计算机上的键盘选择并激活问号按钮几次。</li> + <li>在触摸屏设备上点击几次问号按钮。</li> + <li>切换按钮应使信息框显示/消失。在上述三种情况中,是否都这样做?</li> + <li>文字可读吗?</li> + <li>信息框在出现/消失时是否能够流畅地生成动画?</li> +</ul> + +<p>运行测试时,最好还是:</p> + +<ul> + <li>尽可能设置单独的浏览器配置文件,禁用浏览器扩展和其他此类操作,并在该配置文件中运行测试(例如,请参阅<a href="https://support.mozilla.org/en-US/kb/profile-manager-create-and-remove-firefox-profiles">Use the Profile Manager to create and remove Firefox profiles</a>和<a href="https://support.google.com/chrome/answer/2364824">Share Chrome with others or add personas</a>)</li> + <li>在可用的情况下运行测试时使用浏览器的私有模式功能(例如,Firefox中的<a href="https://support.mozilla.org/en-US/kb/private-browsing-use-firefox-without-history">Private Browsing</a>,Chrome中的<a href="https://support.google.com/chrome/answer/95464">Incognito Mode</a>),因此不会保存cookie和临时文件等内容。</li> +</ul> + +<p>这些步骤旨在确保您正在测试的浏览器尽可能“纯粹(pure)”,即没有安装任何可能影响测试结果的内容。</p> + +<div class="note"> +<p><strong>注意</strong>:如果您有可用的硬件,另一个有用的低功耗选项是在低端手机/其他设备上测试您的网站 - 随着网站变得更大并且具有更多效果,网站放慢速度的可能性更高,因此您需要开始给予表现更多考虑。尝试在低端设备上运行您的功能将使更高端设备的体验更有可能。</p> +</div> + +<div class="note"> +<p><strong>注意</strong>:某些服务器端开发环境提供了有用的机制,可以将站点更改部署到仅部分用户,从而提供了一种有用的机制,可以在不需要单独的开发服务器的情况下获取由用户子集测试的功能。示例:<a href="https://github.com/jsocol/django-waffle">Django Waffle Flags</a>。</p> +</div> + +<h2 id="总结">总结</h2> + +<p>阅读本文后,您现在应该知道如何识别目标受众/目标浏览器列表,然后在该列表上有效地执行跨浏览器测试。</p> + +<p>接下来,我们将把注意力转向测试可能发现的实际代码问题,从HTML和CSS开始。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Cross_browser_testing/Introduction","Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS", "Learn/Tools_and_testing/Cross_browser_testing")}}</p> + + + +<p><font face="x-locale-heading-primary, zillaslab, Palatino, Palatino Linotype, x-locale-heading-secondary, serif"><span style="font-size: 37.33327865600586px;"><strong>本章内容</strong></span></font></p> + +<p> + </p><ul> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Introduction">跨浏览器测试简介</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Testing_strategies">进行测试的策略</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/HTML_and_CSS">处理常见的HTML和CSS问题</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/JavaScript">处理常见的JavaScript问题</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility">处理常见的辅助功能问题</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection">执行特性检测</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Automated_testing">自动化测试简介</a></li> + <li><a href="/zh-CN/docs/Learn/Tools_and_testing/Cross_browser_testing/Your_own_automation_environment">搭建你自己的自动化测试环境</a></li> + </ul> diff --git a/files/zh-cn/learn/tools_and_testing/github/index.html b/files/zh-cn/learn/tools_and_testing/github/index.html new file mode 100644 index 0000000000..e7285a7a92 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/github/index.html @@ -0,0 +1,78 @@ +--- +title: Git 和 GitHub +slug: Learn/Tools_and_testing/GitHub +translation_of: Learn/Tools_and_testing/GitHub +--- +<div>{{LearnSidebar}}{{draft}}</div> + +<p class="summary">所有开发者都将使用到<strong>版本控制系统</strong> (<strong> V</strong>ersion <strong>C</strong>ontrol <strong>S</strong>ystem, 简称<strong> VCS</strong> ), 这种工具让他们在分工合作时避免了不必要的重复与冲突,如果遇到什么问题,也可以及时回退到之前的版本。当今最流行的<strong>版本控制系统</strong>(至少在网络开发者中是这样的)是<strong> Git</strong>,和与之关联的编程社区网站<strong> GitHub</strong> 。这篇短文将带你简单地了解他们。</p> + +<h2 id="主要介绍"><strong>主要介绍</strong></h2> + +<p>版本控制系统在软件开发过程中是必不可少的:</p> + +<ul> + <li>我们很少独自完成一个项目,而在分工合作的同时我们都会有与他人的工作相冲突的风险:尤其是当两个人同时尝试修改同一段代码的时候。所以我们需要有相应的机制用以避免这种情况。</li> + <li>在开发一个项目的时候,我们希望能将代码及时保存,这样就可以避免像电脑突然崩溃辛苦全部白费这样的尴尬局面。</li> + <li>如果后期发现了问题,我们可能还会需要退回更早的版本。有的小朋友也许想到可以通过创建一堆 <code>myCode.js</code>, <code>myCode_v2.js</code>, <code>myCode_v3.js</code>, <code>myCode_final.js</code>, <code>myCode_really_really_final.js</code> 之类的文件用于保存历史版本,但这个方法不妥,容易出错。</li> + <li>不同的团队成员也会需要创建他们自己的独特的版本(在Git中叫做<strong>branches</strong> (分支)),他们在这里添加一些新的功能特性,然后通过一些可控的方法(在 GitHub 中我们使用 <strong>pull request</strong> (拉取请求))将它们贡献到原来的主干项目中。</li> +</ul> + +<p>版本控制系统提供了能够满足以上需求的工具。<a href="https://git-scm.com/">Git</a> 是版本控制系统的典范,而 <a href="https://github.com/">GitHub</a> 是一个为个人或团队操作 Git 储存库 ( Git Repositories) 提供了 Git 服务器和一系列非常实用的工具的网站+基础设施。它提供了报告代码错误、检查工具以及分配任务和任务状态等项目管理工具等等。</p> + +<div class="blockIndicator note"> +<p><strong>温馨提示</strong>: Git 实际上是一个分散式的版本控制系统,这意味你和其他所有人的电脑上都可以有一个这个复制了这个项目所有源代码的储存库的副本。你在自己的副本上进行了修改,然后提交给服务器,在那里将由这个项目的管理者来决定是否将你的修改添加到主本中。</p> +</div> + +<h2 id="事先准备">事先准备</h2> + +<p>为了使用 Git 和 GitHub,你首先需要:</p> + +<ul> + <li>安装了 Git 的台式电脑(详见 <a href="https://git-scm.com/downloads">Git 下载网址</a>)。</li> + <li>用于使用 Git 的工具。你可以使用一个 <a href="https://git-scm.com/downloads/guis/">Git 图形操作界面客户端</a> (我们推荐 GitHub Desktop 、 Source Tree 或者 Git Kraken)或者也可以直接使用命令行,这取决于你的喜好。事实上,尽管你打算使用图形界面进行操作,了解一些基本的 Git 命令行指令也或许会比较有用。</li> + <li><a href="https://github.com/join">GitHub 账户</a>。如果还没有的话,现在就通过这个链接去注册一个吧!</li> +</ul> + +<p>在此之前你并不一定要有关于网络开发、Git 和 GitHub 或者版本控制系统的任何知识。但是最好还是能有一点基础的计算机使用技能并且懂一点编程,以及有一些可以存放进储存库的代码。</p> + +<p>建议你最好还能够有一些基础的命令行终端的知识,例如:切换目录、新建文件以及修改系统路径(<code>PATH</code>)。</p> + +<div class="blockIndicator note"> +<p><strong>温馨提示: </strong>Github 并不是使用 Git 的唯一途径。还有很多像 <a href="https://about.gitlab.com/">GitLab </a>这样的选择值得你去尝试。你也可以尝试着去构建你自己的 Git 服务器来代替 Github 的功能。在这里我们只是将 Github 作为一种可行的途径进行介绍。</p> +</div> + +<h2 id="带你入门">带你入门</h2> + +<p>先提醒一下这些链接将会带你去访问一些外部资源。我们最终将致力于开发我们专属的 Git 和 GitHub 教程,但现在,这些资料将会带你轻松入门。</p> + +<dl> + <dt><a href="https://guides.github.com/activities/hello-world/">Hello World【 欢迎来到新世界】 (来自 GitHub)</a></dt> + <dd>这里是开始使用 GitHub 的正大门,在这里你可以学到 Git 的基础操作,例如:创建<strong>储存库 (Repository) </strong>和<strong>分支 (Brunch) </strong>、<strong>确认提交 (commit)</strong> 以及打开和合并<strong>拉取请求</strong> (open/merge <strong>Pull Request</strong>)。</dd> + <dt><a href="https://guides.github.com/introduction/git-handbook/">Git Handbook 【使用手册】 (来自 GitHub)</a></dt> + <dd>这个 Git 使用手册就稍微更深入一点了,它解释了什么是<strong>版本控制系统</strong>、什么是<strong>储存库</strong> <strong>(Repository) </strong>、<strong> GitHub 模型</strong>如何运行、 <strong>Git 指令</strong>和示例等。</dd> + <dt><a href="https://guides.github.com/activities/forking/">Forking Projects 【复刻项目】 (来自 GitHub)</a></dt> + <dd>当你想要对别人的代码做出贡献时,<strong>复刻</strong>(<strong>fork</strong>,即服务端代码仓库复制)是必不可少的步骤。这个链接将告诉你如何操作。</dd> + <dt><a href="https://help.github.com/en/articles/about-pull-requests">About Pull Requests 【关于拉取请求】 (来自 GitHub)</a></dt> + <dd>这是一个管理拉取请求的有用的指南:你所修改的代码需要通过拉取请求来让项目的管理者决定是否采纳。</dd> + <dt><a href="https://guides.github.com/features/issues/">Mastering issues 【处理问题】 (来自 GitHub)</a></dt> + <dd><strong>问题区</strong> (<strong>Issues</strong>) 就像是一个关于你这个 GitHub 项目的论坛,在这里人们可以提问题和报告错误,你可以管理更新(例如给人们分配需要解决的问题、澄清问题以及告知问题已经解决)。这篇文章会让你知道所需要的一些关于问题区的知识。</dd> +</dl> + +<div class="blockIndicator note"> +<p><strong>温馨提示:</strong> 在 Git 和 GitHub 上面你还可以做一大堆事情,但我们认为,如果你想要有效地使用 Git ,上面的这些知识是至少应该具备的。当你更深入地了解 Git 时,你将会意识到,当你开始使用更加复杂的指令时会更容易出错。但不要担心,即使是专业的网络工程师有时都会感到困惑,并通过网络检索或 <a href="https://github.com/k88hudson/git-flight-rules">Flight rules for Git</a> 和<a href="https://dangitgit.com/"> Dangit, git!</a> 这样的网站来寻找答案。</p> +</div> + +<h2 id="还可以看看">还可以看看</h2> + +<ul> + <li><a href="https://guides.github.com/introduction/flow/">Understanding the GitHub flow 【理解 GitHub 流程】</a></li> + <li><a href="https://git-scm.com/docs">Git command list 【指令列表】</a></li> + <li><a href="https://guides.github.com/features/mastering-markdown/">Mastering markdown 【掌握 Markdown 格式】</a> (在网页上、评论区常用的格式以及 <code>.md</code> 文件所使用的格式,GitHub 中的介绍文件 (readme.md) 即用这种格式书写)。</li> + <li><a href="https://guides.github.com/features/pages/">Getting Started with GitHub Pages 【入门 Github 页面】</a> (如何在 GitHub 上发布示例和网站)。</li> + <li><a href="https://learngitbranching.js.org/">Learn Git branching 【学习 Git 的分支结构】</a></li> + <li><a href="https://github.com/k88hudson/git-flight-rules">Flight rules for Git 【Git 中的飞行法则】</a> ( 在Git中实现特定功能的非常有用的方法介绍纲要,包括如何在出错时纠错等)。</li> + <li> + <p><a href="https://dangitgit.com/">Dangit, git! 【#网络和谐#,Git !】</a>(另一个十分有用的方法介绍纲要,特别是在出错的时候进行纠正的方法)。</p> + </li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/index.html b/files/zh-cn/learn/tools_and_testing/index.html new file mode 100644 index 0000000000..064d064027 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/index.html @@ -0,0 +1,39 @@ +--- +title: 工具和测试 +slug: Learn/Tools_and_testing +tags: + - CSS + - HTML + - JavaScript + - 工具 + - 测试 + - 自动化 + - 跨浏览器 +translation_of: Learn/Tools_and_testing +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">一旦你开始使用web的核心技术(如HTML,CSS和Javascript)进行编程,并且开始获得更多的体验,读更多的资料,学习更多的提示和技巧,你就会遇到各种工具,从优化的CSS和Javascript到测试和自动化应用,再到其他更多的领域。当你的web项目变得更大更复杂,如果你希望利用一些工具为你的代码做出可靠的测试计划。本节教程旨在帮助你开始并做出明智的选择。</p> + +<p>web行业是个让人兴奋的地方,但它也有并发症。现在我们用来构建网站的核心技术是相当稳定的,但是新的特性一直不断地加进来,还有建立在这些技术之上的能够方便我们工作的新的工具,都在不断出现。在此之前,我们仍然需要保持前台的跨浏览器支持,并且保证我们的代码遵循最佳实践以允许我们的项目可以工作在不同的浏览器和设备上,让我们的用户能够正常浏览我们的网页,还要保证对残障人士可用。</p> + +<p>找出你应该用什么样的工具是个困难的过程,所以我们这系列文章来告诉你哪些工具是可用的,它们能为你做什么,和如何利用行业的热门工具。</p> + +<div class="note"> +<p><strong>提示</strong>: 因为一直以来都是新的工具出现伴随着旧的工具过时,所以我们有意将材料写得尽可能地中立—我们想优先关注那些能够帮你完成通用类型任务的工具,而不是一些特殊工具。我们显然需要展示某些工具的用途来演示特殊的技术,但是要清楚我们并不是把它们作为最好的或者唯一的工具进行推荐—大多数情况下有很多其他的方式可以备选,我们只是想提供给你一个清晰的可用的方法。</p> +</div> + +<h2 id="学习途径">学习途径</h2> + +<p>尝试详细地使用这些工具之前,你首先应该学习 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>,和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 的基础知识。 比如,在你开始调试复杂web代码之前,你需要知道这些语言的基础知识,或者能有效地使用Javascript库,或者能够使用测试工具为你的代码编写和运行测试等等。</p> + +<p>首先你需要一个扎实的基础。</p> + +<h2 id="模块">模块</h2> + +<dl> + <dt>真实世界web开发工具(TBD)</dt> + <dd>在这个模块中,我们探索各种可用的web开发工具。这包括审查你不得不解决的最基本的任务,如何让它们在一个工作流中协调配合以及目前能够完成这些任务的最好的工具。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing">跨浏览器测试</a></dt> + <dd>这个模块在测试web项目跨不同浏览器的领域看起来很特别。我们要识别你的目标受众(比如,你最担心的是哪些用户、浏览器和设备?),如何做测试,主要的问题是你将面临不同类型的代码和如何修复它们,有什么有用的工具能够帮助你测试和修复问题,如何通过自动化来增加测试效率。</dd> +</dl> diff --git a/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/command_line/index.html b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/command_line/index.html new file mode 100644 index 0000000000..ce0d00fee7 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/command_line/index.html @@ -0,0 +1,1096 @@ +--- +title: Command line crash course +slug: Learn/Tools_and_testing/Understanding_client-side_tools/Command_line +tags: + - CLI + - npm + - 命令行 + - 学习 + - 客户端 + - 工具 + - 开发者 + - 终端 +translation_of: Learn/Tools_and_testing/Understanding_client-side_tools/Command_line +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/Tools_and_testing/Understanding_client-side_tools/Overview","Learn/Tools_and_testing/Understanding_client-side_tools/Package_management", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</div> + +<p class="summary">在您的开发过程中,您无疑需要在终端上运行一些命令(或者在“命令行”上,它们实际上是相同的)。本文介绍了终端、需要输入的基本命令、如何将命令链接在一起,以及如何添加自己的命令行接口(CLI)工具。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉核心的 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 语言。</td> + </tr> + <tr> + <th scope="row">目标:</th> + <td>要了解什么是终端/命令行,应该学习什么基本命令,以及如何安装新的命令行工具。</td> + </tr> + </tbody> +</table> + +<h2 id="欢迎使用终端">欢迎使用终端</h2> + +<p>终端是一个文本界面,用于执行基于文本的程序。如果您正在运行任何用于web开发的工具,那么几乎可以保证您必须打开命令行并运行一些命令来使用您所选择的工具(您经常会看到这样的工具被称为<strong>CLI工具</strong>命令行接口工具)。</p> + +<p>大量的工具可以通过在命令行中输入命令来使用;许多是预先安装在您的系统上的,还有大量其他的可以从包注册表中安装。包注册表类似于应用程序商店,但(主要)用于基于命令行的工具和软件。我们将在本章后面看到如何安装一些工具,在下一章我们将学习更多关于包注册表的知识。</p> + +<section id="sect1"> +<article> +<section id="sect2"> +<p>对命令行最大的一个批评是它在用户体验方面非常缺乏。第一次查看命令行可能是一种令人畏惧的体验:空白屏幕和闪烁的光标,对于要做什么几乎没有明显的帮助。</p> +</section> +</article> +</section> + +<section id="sect3"> +<article> +<section id="sect4"> +<p>表面上看,它们并不受欢迎,但您可以使用它们做很多事情,我们保证,通过一些指导和练习,使用它们会变得更容易!这就是为什么我们提供这一章来帮助你在这个看似不友好的环境中开始。</p> +</section> +</article> +</section> + +<h3 id="终端从何而来?">终端从何而来?</h3> + +<section id="sect5"> +<article> +<section id="sect6"> +<p>终端机起源于20世纪五六十年代,它最初的形式与我们今天使用的并不相似(这是我们应该感谢的)。你可以在维基百科的词条中读到一些历史 <a href="https://en.wikipedia.org/wiki/Computer_terminal">Computer Terminal</a>。</p> +</section> +</article> +</section> + +<section id="sect7"> +<article> +<section id="sect8"> +<p>从那时起,终端一直是所有操作系统的一个不变的特征,从台式电脑到隐藏在云中的服务器(它并不是真正的云),到像树莓PI Zero这样的微型计算机,甚至到移动电话。它提供了对计算机底层文件系统和底层特性的直接访问,因此,如果您知道自己在做什么,它对于快速执行复杂任务非常有用。</p> +</section> +</article> +</section> + +<section id="sect9"> +<article> +<section id="sect10"> +<p>例如,编写一个命令来立即更新数百个文件的标题,例如从ch01-xxxx.png更新到ch02-xxxx.png,这对于自动化也很有用。如果您使用finder或explorer GUI应用程序更新文件名,这将花费您很长时间。</p> +</section> +</article> +</section> + +<p>总而言之,终端不会很快消失。</p> + +<h3 id="终端像什么呢?">终端像什么呢?</h3> + +<section id="sect11"> +<article> +<section id="sect12"> +<p>下面你可以看到一些不同口味的程序,你可以得到一个终端。</p> +</section> +</article> +</section> + +<section id="sect13"> +<article> +<section id="sect14"> +<p>下面的图片显示了Windows中可用的命令提示,有很多种选项,从“cmd”程序到“powershell”,可以在开始菜单中输入程序名称运行。</p> +</section> +</article> +</section> + +<p><img alt="A vanilla windows cmd line window, and a windows powershell window" src="https://mdn.mozillademos.org/files/17183/win-terminals.png" style="border-style: solid; border-width: 1px; display: block; height: 261px; margin: 0px auto; width: 900px;"></p> + +<section id="sect15"> +<article> +<section id="sect16"> +<p>下面是macOS终端应用程序。</p> +</section> +</article> +</section> + +<p><img alt="A basic vanilla mac terminal" src="https://mdn.mozillademos.org/files/17180/mac-terminal.png" style="display: block; height: 223px; margin: 0px auto; width: 500px;"></p> + +<h3 id="你如何进入终端?">你如何进入终端?</h3> + +<p>现在许多开发人员都在使用基于unix的工具(例如,终端,以及你可以通过它访问的工具)。目前web上存在的许多教程和工具都支持(可悲的是假定)基于unix的系统,但不用担心它们在大多数系统上都可用。在本节中,我们将了解如何访问所选系统上的终端。</p> + +<h4 id="LinuxUnix">Linux/Unix</h4> + +<p>如上所述,Linux / Unix系统默认情况下在应用程序中列出了一个可用的终端。</p> + +<h4 id="macOS">macOS</h4> + +<section id="sect17"> +<article> +<section id="sect18"> +<p>macOS有一个名为Darwin的系统,它位于图形用户界面的下方。Darwin是类unix系统,它提供了终端和对底层工具的访问。macOS Darwin在很大程度上与Unix不相上下,当然在阅读本文时不会给我们带来任何担忧。</p> +</section> +</article> +</section> + +<section id="sect19"><span>终端可在macOS的“应用程序/实用程序/终端”上使用。</span></section> + +<h4 id="Windows">Windows</h4> + +<section id="sect20"> +<article> +<section id="sect21"> +<p>与其他一些编程工具一样,在Windows上使用终端(或命令行)传统上并不像在其他操作系统上那样简单。但情况正在好转。</p> +</section> +</article> +</section> + +<section id="sect22"> +<article> +<section id="sect23"> +<p>很长一段时间以来,Windows一直有自己的名为cmd(命令提示符)的类似于终端的程序,但这显然与Unix命令不同,它相当于老式的Windows DOS提示符。</p> +</section> +</article> +</section> + +<section id="sect24"> +<article> +<section id="sect25"> +<p>更好的程序可以在Windows上提供终端体验,比如Powershell (<a href="https://github.com/PowerShell/PowerShell">see here to find installers</a>), 和 Gitbash (作为一部分 <a href="https://gitforwindows.org/">git for Windows</a> 工具箱)</p> +</section> +</article> +</section> + +<section id="sect26"> +<article> +<section id="sect27"> +<p>然而,在现代,Windows的最佳选择是Windows Linux子系统(WSL),它是一个兼容层,用于从Windows 10中直接运行Linux操作系统,允许您直接在Windows上运行真正的终端,而不需要虚拟机。</p> +</section> +</article> +</section> + +<section id="sect28"> +<article> +<section id="sect29"> +<p>这可以直接从Windows商店免费安装。在目录中可以找到所需的所有文档 <a href="https://docs.microsoft.com/en-us/windows/wsl">Windows Subsystem for Linux Documentation</a>.</p> +</section> +</article> +</section> + +<p><img alt="a screenshot of the windows subsystem for linux documentation" src="https://mdn.mozillademos.org/files/17184/wsl.png" style="border-style: solid; border-width: 1px; display: block; height: 665px; margin: 0px auto; width: 1000px;"></p> + +<section id="sect30"> +<article> +<section id="sect31"> +<p>至于在Windows上选择什么选项,我们强烈建议尝试安装WSL。您可以坚持使用默认的命令提示符(cmd),许多工具都可以正常工作,但是您会发现,如果与Unix工具具有更好的一致性,那么一切都会变得更容易。</p> +</section> +</article> +</section> + +<h4 id="边注:命令行和终端的区别是什么?">边注:命令行和终端的区别是什么?</h4> + +<section id="sect32"> +<article> +<section id="sect33"> +<p>通常你会发现这两个术语可以互换使用。从技术上讲,终端是启动并连接到shell的软件。shell是您的会话和会话环境(提示符和快捷方式等内容可以在其中定制)。命令行是您输入命令并且光标闪烁的文字行。</p> +</section> +</article> +</section> + +<h3 id="你必须使用终端吗">你<em>必须</em>使用终端吗</h3> + +<section id="sect34"> +<article> +<section id="sect35"> +<p>尽管命令行提供了大量的工具,但是如果您使用的工具是这样像 <a href="https://code.visualstudio.com/">Visual Studio Code</a></p> + +<section id="sect36"> +<article> +<section id="sect37"> +<p>还有大量的扩展可以作为代理使用终端命令,而不需要直接使用终端。但是,您不会找到一个代码编辑器扩展来满足您想要做的所有事情 — 你最终将会获得一些使用终端的经验。</p> +</section> +</article> +</section> +</section> +</article> +</section> + +<h2 id="基本的内置终端命令">基本的内置终端命令</h2> + +<section id="sect38"> +<article> +<section id="sect39"> +<article> +<section id="sect40"> +<p>说的够多了,让我们开始看看一些终端命令吧!下面是命令行可以做的一些事情,以及每种情况下相关工具的名称</p> +</section> +</article> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect41"> + <article> + <section id="sect42"> + <p>导航计算机的文件系统以及基本级别的任务,如创建、复制、重命名和删除:</p> + </section> + </article> + </section> + + <ul> + <li> + <section id="sect43"> + <article> + <section id="sect44"> + <p>移动您的目录结构 :<span> </span><code>cd</code></p> + </section> + </article> + </section> + </li> + <li>建立目录: <code>mkdir</code></li> + <li>创建文件(修改他们的原数据): <code>touch</code></li> + <li>复制文件: <code>cp</code></li> + <li>移动文件: <code>mv</code></li> + <li>删除文件或目录: <code>rm</code></li> + </ul> + </li> + <li>下载在特定的url找到的文件: <code>curl</code></li> + <li>在较大的文件体中寻找特定的片段: <code>grep</code></li> + <li>主页查看文件的内容: <code>less</code>, <code>cat</code></li> + <li>操作和转换文本流(例如,讲HTML文件中<div>的所有实例改为<article>): <code>awk</code>, <code>tr</code>, <code>sed</code></li> +</ul> + +<div class="blockIndicator note"> +<p>注意:在web上有许多很好的教程深入了解web上的命令行——这只是一个简短的介绍</p> +</div> + +<section id="sect45"> +<article> +<section id="sect46"> +<p>让我们继续,看看在命令行上使用这些工具中的几个。在进一步操作之前,先打开终端程序</p> +</section> +</article> +</section> + +<h3 id="在命令行中导航">在命令行中导航</h3> + +<section id="sect47"> +<article> +<section id="sect48"> +<p>当您访问命令行时,您将不可避免地需要导航到一个特定的目录“做一些事情”。所有的操作系统(假设是默认设置)都将在您的“home”目录中启动它们的终端程序,从那里您可能想要移动到另一个地方。</p> +</section> +</article> +</section> + +<p> <code>cd</code> 命令允许您更改目录。从技术上讲,cd不是一个程序,而是内置的。这意味着您的操作系统可以开箱即用地提供它,而且您也不会意外地删除它,感谢上帝!您不需要过多地担心某个命令是否是内置的,但是要记住,内置的命令会在所有基于unix的系统上出现。</p> + +<section id="sect49"> +<article> +<section id="sect50"> +<p>要更改目录,请在终端中键入cd,然后输入要移动到的目录。假设该目录在您的主目录中,您可以使用 <code>cd Desktop</code> (请参见下面的屏幕截图).</p> +</section> +</article> +</section> + +<p><img alt="results of the cd Desktop command being run in a variety of windows terminals - the terminal location moves into the desktop" src="https://mdn.mozillademos.org/files/17182/win-terminals-cd.png" style="border-style: solid; border-width: 1px; display: block; height: 349px; margin: 0px auto; width: 500px;"></p> + +<section id="sect51"> +<article> +<section id="sect52"> +<p>试着把这个输入到你的系统终端</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd Desktop</pre> + +<section id="sect53"> +<article> +<section id="sect54"> +<p>如果您想回到上一个目录,您可以使用两个点</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd ..</pre> + +<div class="blockIndicator note"> +<section id="sect55"> +<article> +<section id="sect56"> +<p><strong>注意</strong>:一个非常有用的终端快捷方式是使用<kbd>tab</kbd> 键自动完成你知道的名字,而不是必须键入整个东西。例如,在键入以上两个命令后,尝试键入 <code>cd D</code> 并按下<kbd>tab</kbd> — 它应该自动完成目录名称 <code>Desktop</code> 对于您,只要它存在于当前目录中。在你前进的过程中记住这一点。</p> +</section> +</article> +</section> +</div> + +<p>如果要转到的目录嵌套得很深,则需要知道访问该目录的路径。 当您更加熟悉文件系统的结构时,这通常会变得更容易,但是如果您不确定路径,通常可以使用ls命令(请参见下文)的组合并在其中单击来确定它的路径。 “资源管理器/查找器”窗口可查看目录相对于当前位置的位置。</p> + +<section id="sect57"> +<article> +<section id="sect58"> +<p>例如,如果您想进入一个名为src的目录,该目录位于桌面的一个名为project的目录中,您可以从您的主文件夹键入这三个命令来到达该目录</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd Desktop +cd project +cd src</pre> + +<section id="sect59"> +<article> +<section id="sect60"> +<p>但这只是浪费时间,相反,您可以键入一个命令,用斜杠分隔路径中的不同项,就像在CSS、HTML或JavaScript代码中指定图像或其他资产的路径一样</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">cd Desktop/project/src</pre> + +<section id="sect61"> +<article> +<section id="sect62"> +<p>例如,请注意,在路径上包含一个前斜线将使路径成为绝对路径 <code>/Users/your-user-name/Desktop</code>. 像我们上面做的那样,省略前导斜杠可以使路径相对于当前的工作目录。这与您在web浏览器中看到的url完全相同。前面的斜杠意味着“在网站的根”,而省略斜杠意味着“这个URL是相对于我当前页面的”。</p> + +<section id="sect63"></section> +</section> +</article> +</section> + +<div class="blockIndicator note"> +<section id="sect64"> +<article> +<section id="sect65"> +<p><strong>注意</strong>:在windows中,你使用反斜杠而不是正斜杠。<code>cd Desktop\project\src</code> — 他的可能看起来很奇怪,但是如果你感兴趣的话, <a href="https://www.youtube.com/watch?v=5T3IJfBfBmI">watch this YouTube clip</a> 微软的一位主要工程师对此进行了解释。</p> +</section> +</article> +</section> +</div> + +<h3 id="列出目录内容">列出目录内容</h3> + +<section id="sect66"> +<article> +<section id="sect67"> +<p>另一个内置的Unix命令是ls (list的缩写),它列出当前所在目录的内容。注意,这不会工作,如果你使用默认的Windows命令提示符(cmd),相当于dir。</p> +</section> +</article> +</section> + +<section id="sect68"> +<article> +<section id="sect69"> +<p>现在试着在终端上运行它</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">ls</pre> + +<section id="sect70"> +<article> +<section id="sect71"> +<p>这提供了当前工作目录中的文件和目录的列表,但这些信息实际上很基本,您只能得到每个项的名称,而不能知道它是文件还是目录,或者其他任何东西。幸运的是,对命令的用法进行一个小小的更改就可以提供更多的信息。</p> +</section> +</article> +</section> + +<h3 id="介绍命令选项">介绍命令选项</h3> + +<section id="sect72"> +<article> +<section id="sect73"> +<p>大多数终端命令都有选项,这些选项是您添加到命令末尾的修饰符,它们使命令的行为略有不同。它们通常由命令名后的空格、后接破折号、后接一个或多个字母组成。</p> +</section> +</article> +</section> + +<p>例如,试一试看,你能得到什么?</p> + +<pre class="brush: bash notranslate">ls -l </pre> + +<p>至于 <code>ls</code>, the <code>-l</code> 选项为您提供每行一个文件或目录的清单,并显示更多信息。可以通过查找行最左边的字母“d”来识别目录。这些是我们可以做到的 <code>cd</code> 进入.</p> + +<section id="sect74"> +<article> +<section id="sect75"> +<p>下面是一个屏幕截图,顶部是一个普通的macOS终端,还有一个定制的终端,添加了一些额外的图标和颜色,让它看起来生动,都显示了运行的结果<code>ls -l</code>:</p> +</section> +</article> +</section> + +<p><img alt="A vanilla mac terminal and a more colorful custom mac terminal, showing a file listing - the result of running the ls -l command" src="https://mdn.mozillademos.org/files/17181/mac-terminals-ls.png" style="border-style: solid; border-width: 1px; display: block; height: 360px; margin: 0px auto; width: 500px;"></p> + +<div class="blockIndicator note"> +<section id="sect76"> +<article> +<section id="sect77"> +<p><strong>注意</strong>:要确切地了解每个命令有哪些可用选项,您可以查看它<a href="https://en.wikipedia.org/wiki/Man_page">man page</a><span>. </span>通过键入<span> </span><code>man</code><span> </span>命令,后跟要查找的命令的名称,例如<code>man ls</code><span>. </span>这将在终端的默认文本文件查看器中打开手册页(例如<span>, </span><code><a href="https://en.wikipedia.org/wiki/Less_(Unix)">less</a></code><span>(在我的终端中),然后您应该能够使用箭头键或其他类似的机制在页面中滚动。手册页详细地列出了所有选项,一开始可能有点吓人,但至少在需要时您知道它就在那里。一旦您完成了对手册页的查看,您需要使用文本查看器的quit命令退出它</span><span>("q" 在 </span><code>less</code><span>;</span><span>如果不明显,你可能需要在网上搜索才能找到</span><span>).</span></p> +</section> +</article> +</section> +</div> + +<div class="blockIndicator note"> +<p><strong>注意</strong>:</p> + +<section id="sect78"> +<article> +<section id="sect79"> +<p>要同时运行具有多个选项的命令,通常可以将它们全部放在破折号后面的单个字符串中<code>ls -lah</code>, 或<code>ls -ltrh</code>.尝试在 <code>ls</code> 了解这些额外选项的作用</p> +</section> +</article> +</section> +</div> + +<section id="sect80"> +<article> +<section id="sect81"> +<p>既然我们已经讨论了两个基本命令,那么稍微浏览一下您的目录,看看是否可以从一个位置导航到下一个位置。</p> +</section> +</article> +</section> + +<h3 id="创建,复制,移动,删除">创建,复制,移动,删除</h3> + +<section id="sect82"> +<article> +<section id="sect83"> +<p>在使用终端时,您可能会经常使用其他一些基本实用程序命令。它们非常简单,所以我们不会像前一对那样详细地解释它们。</p> +</section> +</article> +</section> + +<section id="sect84"></section> + +<section id="sect85"> +<article> +<section id="sect86"> +<p>在您创建的某个地方的测试目录中使用它们,这样您就不会意外地删除任何重要的内容,使用下面的示例命令作为指导</p> +</section> +</article> +</section> + +<section id="sect87"></section> + +<ul> + <li><code>mkdir</code> —这将在您所在的当前目录中创建一个新目录,名称是您在命令名之后提供的。例如<code>mkdir my-awesome-website将创建一个新目录叫</code><code>my-awesome-website</code><span>.</span></li> + <li><code>rmdir</code> —删除指定目录,但仅当它为空时。例如<code>rmdir my-awesome-website</code> + <section id="sect88"> + <article> + <section id="sect89"> + <p>将删除我们在上面创建的目录。如果您希望删除一个非空的目录(并删除其中包含的所有内容),则可以使用<code>-r</code><span> </span><span>选项(递归),但这很危险。 确保以后在目录中不需要任何内容,因为它将永远消失。</span></p> + </section> + </article> + </section> + </li> + <li><code>touch</code> —在当前目录中创建一个新的空文件。例如<code>touch mdn-example.md</code><span> 创建一个新的空文件叫做</span><span> </span><code>mdn-example.md</code><span>.</span></li> + <li><code>mv</code> — + <section id="sect90"> + <article> + <section id="sect91"> + <p>例如,将文件从第一个指定的文件位置移动到第二个指定的文件位置<code>mdn-example.md mdn-example.txt</code><span>(这些位置被写成文件路径)。此命令移动一个名为</span><code>mdn-example.md</code><span>在当前目录中调用一个文件</span><code>mdn-example.txt</code><span> in the current directory.从技术上讲,文件正在被移动,但是从实际的角度来看,这个命令实际上是在重命名文件。</span></p> + </section> + </article> + </section> + </li> + <li><code>cp</code> — 类似于 <code>mv</code>, <code>cp</code>在指定的第一个位置和第二个位置创建文件的副本。例如 + <section id="sect92"><span>, </span><code>cp mdn-example.txt mdn-example.txt.bak<span>创建一个副本</span></code><code>mdn-example.txt</code><span> 叫做 </span><code>mdn-example.txt.bak</code><span>(当然,如果你愿意,你也可以叫它别的名字)。</span></section> + </li> + <li><code>rm</code> —删除指定的文件。例如, <code>rm mdn-example.txt</code><span> 删除单个文件叫做 </span><code>mdn-example.txt</code><span>.</span>请注意,此删除是永久性的,不能通过桌面用户界面上的回收站撤消。</li> +</ul> + +<div class="blockIndicator note"> +<p><strong>注意</strong>: 许多终端命令允许您使用星号作为“通配符”字符,意思是“任何字符序列”。这允许您一次对可能大量的文件运行操作,所有这些文件都匹配指定的模式。作为一个例子<span>, </span><code>rm mdn-*</code><span> </span>将删除所有文件开头<code>mdn-</code><span>. </span><code>rm mdn-*.bak</code><span> </span>会删除所有文件的开头<code>mdn- </code>结束<span> </span><code>.bak</code><span>.</span></p> +</div> + +<h2 id="考虑终端有害吗?">考虑终端有害吗?</h2> + +<section id="sect93"><span>我们之前提到过这一点,但为了明确起见,您需要小心使用终端。简单的命令不会带来太多危险,但是当您开始将更复杂的命令组合在一起时,您需要仔细考虑该命令将执行什么操作,并在最终在指定的目录中运行它们之前先尝试测试它们。</span></section> + +<section id="sect94"></section> + +<section id="sect95"> +<article> +<section id="sect96"> +<p>假设您在一个目录中有1000个文本文件,您想遍历它们,并且只删除文件名中有特定子字符串的文件。如果不小心,可能会删除一些重要的内容,在此过程中丢失大量工作。</p> +</section> +</article> +</section> + +<section id="sect97"><span>要养成的一个好习惯是在文本编辑器中编写您的terminal命令,弄清楚您认为它应该是什么样子,然后对目录进行备份,并首先尝试在该备份上运行命令,以测试它。</span></section> + +<section id="sect98"> +<article> +<section id="sect99"> +<p><span>另一个好建议是,如果你不习惯在自己的机器上尝试终端命令,可以在Glitch.com上试试。除了是测试web开发代码的好地方外,这些项目还允许您访问终端,这样您就可以直接在该终端中运行所有这些命令,而且不会破坏您自己的机器。</span></p> +</section> +</article> +</section> + +<p><img alt="a double screenshot showing the glitch.com home page, and the glitch terminal emulator" src="https://mdn.mozillademos.org/files/17179/glitch.png" style="height: 848px; width: 900px;"></p> + +<section id="sect100"> +<article> +<section id="sect101"> +<p>快速浏览特定终端命令的一个很好的资源是 <a href="https://tldr.sh/">tldr.sh</a>. 这是一个社区驱动的文档服务,类似于MDN,但特定于终端命令。</p> + +<section id="sect102"><span>在下一节中,让我们更进一步(实际上是几个层次),看看如何在命令行上将工具连接在一起,从而真正了解终端如何优于常规的桌面用户界面。</span></section> +</section> +</article> +</section> + +<h2 id="与管道命令连接在一起">与管道命令连接在一起</h2> + +<section id="sect103"></section> + +<section id="sect104"> +<article> +<section id="sect105"> +<p>当您开始使用。将命令链接在一起时,终端才真正成为自己的终端 <code>|</code> (pipe) 的象征。让我们看一个简单的例子来说明这意味着什么。</p> +</section> +</article> +</section> + +<p>我们已经看了 <code>ls</code>, 它可以输出文件目录:</p> + +<pre class="brush: bash notranslate">ls</pre> + +<section id="sect106"> +<article> +<section id="sect107"> +<p>但是如果我们想要快速计算当前目录中的文件和目录的数量会怎样呢<code>ls</code> 不能单独运行</p> +</section> +</article> +</section> + +<section id="sect108"> +<article> +<section id="sect109"> +<p>还有另一个可用的Unix工具,称为<code>wc</code>. 它计算输入到其中的单词、行、字符或字节的数量。这可以是一个文本文件,下面的示例输出其中的行数</p> +</section> +</article> +</section> + +<p><code>myfile.txt</code>:</p> + +<pre class="brush: bash notranslate">wc -l myfile.txt</pre> + +<section id="sect110"> +<article> +<section id="sect111"> +<p>但是它还可以计算输入到它的输出的行数。例如,下面的命令计算<span class="seoSummary">ls</span>命令输出的行数(如果单独运行,它通常会打印到终端),并计算到终端的输出</p> +</section> +</article> +</section> + +<section id="sect112"></section> + +<pre class="brush: bash notranslate">ls | wc -l</pre> + +<p><code><font face="Arial, x-locale-body, sans-serif"><span style="background-color: #ffffff;">因为</span></font>ls</code> 在自己的行上打印每个文件或目录,这有效地为我们提供了目录和文件计数。</p> + +<section id="sect113"> +<article> +<section id="sect114"> +<p>这是怎么回事?(unix)命令行工具的一般原理是,它们将文本打印到终端(也称为“打印到标准输出”或<code>STDOUT</code>). 很多命令也可以从流输入(称为“标准输入”o)中读取内容<span> </span><code>STDIN</code><span>).</span></p> +</section> +</article> +</section> + +<section id="sect115"> +<article> +<section id="sect116"> +<p>管道操作符可以将这些输入和输出连接在一起,允许我们构建越来越复杂的操作,以满足我们的需要。一个命令的输出可以成为下一个命令的输入。在这种情况下, <code>ls</code> 通常会将其输出到<code>STDOUT</code>, 但是相反 <code>ls</code>输出被制成<code>wc</code>, 它将该输出作为输入,计算它包含的行数,然后将该计数输出到STDOUT。</p> +</section> +</article> +</section> + +<h2 id="一个稍微复杂一点的例子">一个稍微复杂一点的例子</h2> + +<section id="sect117"> +<article> +<section id="sect118"> +<p>让我们看一些更复杂的东西。我们将首先尝试获取MDN的“获取”页面的内容 <code>curl</code> 命令(可用于从url请求内容)<a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch" style="">https://developer.mozilla.org/en-US/docs/Web/API/fetch</a><span style="">.</span></p> +</section> +</article> +</section> + +<section id="sect119"> +<article> +<section id="sect120"> +<p>但是,这个URL是页面的旧位置。如果您在一个新的浏览器标签中输入它,您将(最终)被重定向到<a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch" style="">https://developer.mozilla.org/enUS/docs/Web/API/WindowOrWorkerGlobalScope/fetch</a><span style="">.</span></p> +</section> +</article> +</section> + +<p>因此,如果您使用curl请求https://developer.mozilla.org/docs/Web/API/fetch,则不会得到输出。 现在就试试:</p> + +<pre class="brush: bash notranslate">curl https://developer.mozilla.org/en-US/docs/Web/API/fetch</pre> + +<p>我们想精确的告诉 <code>curl</code> 遵循重定向使用<code>-L</code> 标签.</p> + +<section id="sect121"> +<article> +<section id="sect122"> +<p>让我们也看看标题 <code>developer.mozilla.org</code> 返回使用 <code>curl</code>'s <code>-I</code> 标签, 并打印它发送到终端的所有位置重定向,通过管道输出 <code>curl</code> 到<code>grep</code> (我们将要求grep返回包含单词“location”的所有行)。</p> + +<p>尝试运行以下代码,您将看到在到达最终页面之前,实际上发生了三次重定向</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">curl https://developer.mozilla.org/docs/Web/API/fetch -L -I | grep location</pre> + +<section id="sect123"> +<article> +<section id="sect124"> +<p>输出应该是这样的 (<code>curl</code> 首先会输出一些下载计数器之类的东西):</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">location: /en-US/docs/Web/API/fetch +location: /en-US/docs/Web/API/GlobalFetch/GlobalFetch.fetch() +location: /en-US/docs/Web/API/GlobalFetch/fetch +location: /en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch</pre> + +<section id="sect125"> +<article> +<section id="sect126"> +<p>尽管有些做作,我们可以把这个结果做得更深入一点,并变换 <code>location:</code> 行内容,将基本的起点添加到每个起点的开始,这样我们就可以打印出完整的url。为此,我们将在混合中添加awk(它是一种类似于JavaScript、Ruby或Python的编程语言,只是要老得多!)</p> +</section> +</article> +</section> + +<p>尝试运行这个:</p> + +<pre class="brush: bash notranslate">curl https://developer.mozilla.org/docs/Web/API/fetch -L -I | grep location | awk '{ print "https://developer.mozilla.org" $2 }'</pre> + +<section id="sect127"> +<article> +<section id="sect128"> +<p>最终的输出应该是这样的</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">https://developer.mozilla.org/en-US/docs/Web/API/fetch +https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/GlobalFetch.fetch() +https://developer.mozilla.org/en-US/docs/Web/API/GlobalFetch/fetch +https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch</pre> + +<section id="sect129"> +<article> +<section id="sect130"> +<p>通过组合这些命令,我们定制了输出以显示完整的url,当我们请求时,Mozilla服务器将通过这些url重定向<code>/docs/Web/API/fetch</code> URL.了解您的系统将在未来几年证明是有用的,您将了解这些单一服务工具是如何工作的,以及它们如何成为您解决小众问题的工具库的一部分。</p> +</section> +</article> +</section> + +<h2 id="添加工具">添加工具</h2> + +<section id="sect131"> +<article> +<section id="sect132"> +<p>现在我们已经了解了系统自带的一些内置命令,让我们看看如何安装和使用第三方CLI工具。</p> +</section> +</article> +</section> + +<section id="sect133"> +<article> +<section id="sect134"> +<p>目前,用于前端web开发的可安装工具的巨大生态系统主要存在于内部 <a href="https://www.npmjs.com">npm</a>, 与Node.js紧密合作的私有的包托管服务。随着时间的推移,您可以期望看到更多的包提供者。</p> +</section> +</article> +</section> + +<p><a href="https://nodejs.org/en/">Installing Node.js</a> 还要安装npm命令行工具(以及一个以npm为中心的补充工具npx),它提供了安装其他命令行工具的网关。js和npm在所有系统上都能工作:macOS、Windows和Linux。</p> + +<section id="sect135"> +<article> +<section id="sect136"> +<p>现在在您的系统上安装npm,转到上面的URL,下载并运行适合您的操作系统的Node.js安装程序。如果出现提示,请确保将npm作为安装的一部分。</p> +</section> +</article> +</section> + +<p><img alt="the node.js installer on windows, showing the option to include npm" src="https://mdn.mozillademos.org/files/17185/npm-install-option.png" style="border-style: solid; border-width: 1px; display: block; height: 469px; margin: 0px auto; width: 600px;"></p> + +<section id="sect137"> +<article> +<section id="sect138"> +<p>尽管我们将在下一篇文章中讨论许多不同的工具,但我们将继续深入研究 <a href="https://prettier.io/">Prettier</a>. Prettier是一种固执己见的代码格式化程序,它只有“很少的选择”。更少的选择往往意味着更简单。考虑到工具在复杂性方面有时会失控,“很少的选项”可能非常有吸引力。</p> +</section> +</article> +</section> + +<h3 id="在哪里下载我们的CLI工具?">在哪里下载我们的CLI工具?</h3> + +<section id="sect139"> +<article> +<section id="sect140"> +<p>在开始安装Prettier之前,有一个问题需要回答:“我们应该安装到哪里?</p> +</section> +</article> +</section> + +<p>用<code>npm</code> 我们可以选择在全球安装工具,因此我们可以在任何地方或本地访问当前项目目录。</p> + +<p>每种方式各有利弊 — 而这张全球安装的利弊清单还远远不够详尽:</p> + +<table class="standard-table"> + <thead> + <tr> + <th scope="col"> + <section id="sect141"> + <article> + <p>全球安装的优点</p> + </article> + </section> + </th> + <th scope="col">全球安装的缺点</th> + </tr> + </thead> + <tbody> + <tr> + <td> + <section id="sect142"> + <article> + <p>任何地方在您的终端</p> + </article> + </section> + </td> + <td>可能与您项目的代码库不兼容</td> + </tr> + <tr> + <td>只下载一次</td> + <td> + <section id="sect143"> + <article> + <section id="sect144"> + <p>您团队中的其他开发人员无法使用这些工具,例如,如果您通过git这样的工具共享代码基。</p> + </section> + </article> + </section> + </td> + </tr> + <tr> + <td> + <section id="sect145"> + <article> + <p>使用较少的磁盘空间</p> + </article> + </section> + </td> + <td> + <section id="sect146"> + <article> + <section id="sect147"> + <p>与前一点相关的是,它使得项目代码更难复制(如果您在本地安装工具,可以将它们设置为依赖项并使用npm进行安装<code style="">npm install</code><span style="">).</span></p> + </section> + </article> + </section> + </td> + </tr> + <tr> + <td> + <section id="sect148"> + <article> + <section id="sect149"> + <p>总是相同的版本</p> + </section> + </article> + </section> + </td> + <td></td> + </tr> + <tr> + <td> + <section id="sect150"> + <article> + <section id="sect151"> + <p>感觉像任何其他unix命令</p> + </section> + </article> + </section> + </td> + <td></td> + </tr> + </tbody> +</table> + +<section id="sect152"> +<article> +<section id="sect153"> +<p>尽管缺点清单比较短,但是全局安装的负面影响可能要比好处大得多。然而,现在我们宁可追求简单,而采用全局安装来保持简单。在下一篇文章中,我们将进一步了解本地安装以及它们的优点。</p> +</section> +</article> +</section> + +<h3 id="下载_Prettier">下载 Prettier</h3> + +<section id="sect154"> +<article> +<section id="sect155"> +<p>对于本文,我们将安装Prettier作为全局命令行实用程序。</p> +</section> +</article> +</section> + +<section id="sect156"> +<article> +<section id="sect157"> +<p>Prettier是一款专门为前端开发人员设计的代码格式化工具,专注于基于javascript的语言,并增加了对HTML、CSS、SCSS、JSON等的支持。Prettier 能:</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect158"><span style="">省去了在所有代码文件中手动保持样式一致的认知开销;Prettier可以自动为您完成此操作。</span></section> + </li> + <li>帮助Web新手以最佳方式完成他们的代码.</li> + <li>安装在任何操作系统上,甚至直接作为项目工具的一部分,以确保从事您的代码工作的同事和朋友使用您正在使用的代码风格。</li> + <li> + <section id="sect159"> + <article> + <section id="sect160"> + <p>配置为在保存时运行、在键入时运行,甚至在发布代码之前运行(使用稍后将在模块中看到的其他工具)。</p> + </section> + </article> + </section> + </li> +</ul> + +<section id="sect161"> +<article> +<section id="sect162"> +<p>安装node之后,打开终端并运行以下命令来安装更漂亮的程序:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">npm install --global prettier</pre> + +<p>命令运行完成后,Prettier工具现在可以在终端中的文件系统中的任何位置使用。</p> + +<section id="sect163"> +<article> +<section id="sect164"> +<p>与许多其他命令一样,不带任何参数运行该命令将提供用法和帮助信息。现在试试这个</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">prettier</pre> + +<section id="sect165"> +<article> +<section id="sect166"> +<p>输出应该是这样的</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">Usage: prettier [options] [file/glob ...] + +By default, output is written to stdout. +Stdin is read if it is piped to Prettier and no files are given. + +…</pre> + +<section id="sect167"> +<article> +<section id="sect168"> +<p>即使它很长,至少浏览一下使用信息也是值得的。它将帮助您更好地理解如何使用该工具。</p> +</section> +</article> +</section> + +<h3 id="尝试_Prettier">尝试 Prettier</h3> + +<section id="sect169"> +<article> +<section id="sect170"> +<p>让我们快速演示一下Prettier,这样您就可以看到它是如何工作的。</p> +</section> +</article> +</section> + +<section id="sect171"></section> + +<section id="sect172"> +<article> +<section id="sect173"> +<p>首先,在文件系统中容易找到的地方创建一个新目录。可能是一个叫做<code>prettier-test</code> 在你的 <code>Desktop</code>.</p> + +<p>现在将以下代码保存在一个名为<code>index.js</code>, 在测试目录中:</p> +</section> +</article> +</section> + +<pre class="brush: js notranslate">const myObj = { +a:1,b:{c:2}} +function printMe(obj){console.log(obj.b.c)} +printMe(myObj)</pre> + +<section id="sect174"> +<article> +<section id="sect175"> +<p>我们可以在代码基上运行得更好,以检查我们的代码是否需要调整。cd到您的目录中,并尝试运行此命令:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">prettier --check index.js</pre> + +<section id="sect176"> +<article> +<section id="sect177"> +<p>你的产出应该是:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">Checking formatting... +index.js +Code style issues found in the above file(s). Forgot to run Prettier?</pre> + +<section id="sect178"> +<article> +<section id="sect179"> +<p>有些代码样式是可以修改的。没有问题。添加 <code>--write</code> option to the prettier命令将修复这些问题,让我们专注于实际编写有用的代码。</p> +</section> +</article> +</section> + +<section id="sect180"> +<article> +<section id="sect181"> +<p>现在尝试运行这个版本的命令:</p> +</section> +</article> +</section> + +<pre class="brush: bash notranslate">prettier --write index.js</pre> + +<p>你可能得到这样的输出:</p> + +<pre class="brush: bash notranslate">Checking formatting... +index.js +Code style issues fixed in the above file(s).</pre> + +<section id="sect182"> +<article> +<section id="sect183"> +<p>但更重要的是,如果你回头看你的JavaScript文件,你会发现它被重新格式化成这样:</p> +</section> +</article> +</section> + +<pre class="brush: js notranslate">const myObj = { + a: 1, + b: { c: 2 }, +}; +function printMe(obj) { + console.log(obj.b.c); +} +printMe(myObj);</pre> + +<section id="sect184"> +<article> +<section id="sect185"> +<p>根据您的工作流(或您选择的工作流),您可以将其作为流程的自动化部分。自动化确实是工具的优势所在;我们的个人偏好是那种无需配置任何东西就能“自动发生”的自动化。</p> +</section> +</article> +</section> + +<section id="sect186"> +<article> +<section id="sect187"> +<p>使用Prettier有许多实现自动化的方法,尽管它们超出了本文的范围,但是有一些很好的在线资源可以提供帮助(已经链接到其中一些)。您可以调用更漂亮的:</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect188"> + <article> + <section id="sect189"> + <p>在将代码提交到git存储库之前,使用<a href="https://github.com/typicode/husky" style="">Husky</a><span style="">.</span></p> + </section> + </article> + </section> + </li> + <li> + <section id="sect190"> + <article> + <section id="sect191"> + <p>当你在代码编辑器中点击“保存”的时候,无论是<a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode" style="">VS Code</a><span style="">, </span><a href="https://atom.io/packages/prettier-atom" style="">Atom</a><span style="">, 或</span><a href="https://packagecontrol.io/packages/JsPrettier" style="">Sublime Text</a><span style="">.</span></p> + </section> + </article> + </section> + </li> + <li> + <section id="sect192"> + <article> + <section id="sect193"> + <p>作为持续集成检查的一部分,可以使用以下工具<a href="https://github.com/features/actions" style="">Github Actions</a><span style="">.</span></p> + </section> + </article> + </section> + </li> +</ul> + +<section id="sect194"> +<article> +<section id="sect195"> +<p>我们个人的偏好是第二个当使用say VS代码时,Prettier会启动并清理每次我们点击保存时需要做的格式化。关于以不同方式使用Prettier,您可以在 <a href="https://prettier.io/docs/en/">Prettier docs</a>.</p> +</section> +</article> +</section> + +<h2 id="尝试其他的工具">尝试其他的工具</h2> + +<section id="sect196"> +<article> +<section id="sect197"> +<p>如果你想尝试更多的工具,这里有一个简短的列表,很有趣的尝试:</p> +</section> +</article> +</section> + +<section id="sect198"></section> + +<ul> + <li><code><a href="https://github.com/sharkdp/bat">bat</a></code> — 一个更好的 <code>cat</code> (<code>cat</code> 用于打印文件内容)。</li> + <li><code><a href="http://denilson.sa.nom.br/prettyping/">prettyping</a></code> — <code>ping</code>在命令行上,但是是可视化的(ping是检查服务器是否有响应的有用工具)。</li> + <li><code><a href="http://hisham.hm/htop/">htop</a></code> —进程查看器,当某些东西使您的CPU风扇的行为像一个喷气发动机,并且您想要识别出错的程序时,它非常有用。</li> + <li><code><a href="https://tldr.sh/#installation">tldr</a></code> —在本章前面提到的,但是可以作为命令行工具使用。</li> +</ul> + +<section id="sect199"> +<article> +<section id="sect200"> +<p>注意,上面的一些建议可能需要使用npm进行安装,就像我们使用Prettier所做的那样。</p> +</section> +</article> +</section> + +<h2 id="总结">总结</h2> + +<p>这使我们结束了对终端/命令行的简短浏览。 接下来,我们将更详细地介绍软件包管理器,以及如何使用它们。</p> + +<p>{{PreviousMenuNext("Learn/Tools_and_testing/Understanding_client-side_tools/Overview","Learn/Tools_and_testing/Understanding_client-side_tools/Package_management", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">Client-side tooling overview</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">Command line crash course</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">Package management basics</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">Introducing a complete toolchain</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">Deploying our app</a></li> +</ul> diff --git a/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/index.html b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/index.html new file mode 100644 index 0000000000..859a634ca9 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/index.html @@ -0,0 +1,37 @@ +--- +title: 理解客户端web开发工具 +slug: Learn/Tools_and_testing/Understanding_client-side_tools +translation_of: Learn/Tools_and_testing/Understanding_client-side_tools +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">客户端工具可能让人望而生畏, 不过这个系列的文章主要目的在于展示一些最常见的客户端工具的用途,阐明可以被你链接使用的工具,如何使用包管理器安装这些工具,以及如何使用命令行操控它们。我们将以一个完整的工具链示例收尾,示例会向你展示如何变得更具生产力。</p> + +<p class="summary"><strong><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">现在就从我们的“客户端工具概览”开始吧</a></strong></p> + +<h2 id="先决条件">先决条件</h2> + +<p>在尝试使用这里列举的工具之前,你应该先好好学习一下 <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, 和 <a href="/en-US/docs/Learn/JavaScript">JavaScript</a> 的核心基础知识。</p> + +<div class="in-page-callout webdev"> +<h3 id="渴望成为一名前端开发人员?">渴望成为一名前端开发人员?</h3> + +<p>我们已经把你实现目标所需要的所有必要信息都放在了一个课程里面。</p> + +<p><a class="cta primary" href="/docs/Learn/Front-end_web_developer">开始</a></p> +</div> + +<h2 id="指南">指南</h2> + +<dl> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">1. 客户端工具概览</a></dt> + <dd>在这篇文章中,我们会简要介绍现代web工具:在web应用开发的生命周期中的何时使用何种工具,以及如何寻求帮助。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">2. 命令行速成课程</a></dt> + <dd>毫无疑问,在开发过程中,你需要在终端(或者在“命令行”,它们其实是同一个东西)中运行一些命令。这篇文章将会提供对终端的介绍,包括一些你需要输入的基本命令,如何将命令链接在一起,以及何如添加你自己的命令行(CLI)工具。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">3. 包管理基础</a></dt> + <dd>在这篇文章中,我们将会详细看一看包管理器,从而理解如何在我们自己的工程中使用它们——安装工程工具依赖、更新等等。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">4. 一个完整的工具链</a></dt> + <dd>在这个系列的最后几篇文章中,我们将会带你构建一个工具链样例,从而帮你巩固前面学到的知识。我们将从设立一个实际的开发环境开始,一路在Netlify上相应的位置放置发布你的应用所需要的转换工具。在这篇文章里,我们将介绍一个设立我们的开发环境以及代码转换工具的例子。</dd> + <dt><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">5. 发布你的应用</a></dt> + <dd>在我们系列的最后一篇文章中,我们将会使用我们在前篇文章中构建的示例工具链,以便我们能够发布我们的样例应用。我们将代码推到Github,使用Netlify进行发布,甚至还会向你展示如何在这个过程中加入一个简单的测试。</dd> +</dl> diff --git a/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/overview/index.html b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/overview/index.html new file mode 100644 index 0000000000..461be03030 --- /dev/null +++ b/files/zh-cn/learn/tools_and_testing/understanding_client-side_tools/overview/index.html @@ -0,0 +1,391 @@ +--- +title: Client-side tooling overview +slug: Learn/Tools_and_testing/Understanding_client-side_tools/Overview +translation_of: Learn/Tools_and_testing/Understanding_client-side_tools/Overview +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/Tools_and_testing/Understanding_client-side_tools/Command_line", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</div> + +<p class="summary">在本文中,我们提供了现代web工具的概述,有哪些工具可用,在web应用程序开发的生命周期中您将在哪里遇到它们,以及如何使用各个工具寻求帮助。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">先决条件:</th> + <td>熟悉核心HTML,CSS和JavaScript语言。</td> + </tr> + <tr> + <th scope="row">目标</th> + <td> + <section id="sect1"> + <article> + <section id="sect2"> + <p>了解有什么类型的客户端工具,以及如何找到工具并获得这些工具的帮助。</p> + </section> + </article> + </section> + </td> + </tr> + </tbody> +</table> + +<h2 id="现代工具概述">现代工具概述</h2> + +<p>随着时间的推移,为网络编写软件已经变得越来越复杂。尽管“手工”编写HTML、CSS和JavaScript仍然是完全合理的,但现在有大量的工具可供开发人员使用,以加快构建web站点或应用程序的过程。</p> + +<p>有一些非常完善的工具已经成为开发社区中常见的“家喻户晓的名字”,并且每天都在编写和发布新的工具来解决特定的问题。您甚至可能发现自己正在编写一个软件来帮助您自己的开发过程,以解决现有工具似乎无法处理的特定问题。</p> + +<p>单个项目中包含的大量工具很容易让人不知所措。同样,像Webpack这样的工具的一个配置文件可能有数百行之长,其中大多数都是神奇的咒语,似乎可以完成工作,但只有大神级工程师才能完全理解</p> + +<section id="sect3"> +<article> +<section id="sect4"> +<p>有时,即使是最有经验的web开发人员也会因为工具问题而陷入困境;甚至在接触到一行应用程序代码之前,都可能浪费数小时来尝试让工具管道工作。如果你发现自己在过去挣扎,那么用担心,你并不孤独。</p> +</section> +</article> +</section> + +<section id="sect5"></section> + +<p>在这篇文章中,我们将不会回答所有关于web工具的所有问题,但我们将为您提供一个了解基本原理的有用起点,你可以从中构建。对于任何复杂的主题,最好从小处开始,然后逐步进行更高级的使用。</p> + +<h2 id="现代工具系统">现代工具系统</h2> + +<p>当今的现代开发人员工具生态系统非常庞大,因此对这些工具正在解决的主要问题有一个大致的概念是非常有用的。如果你跳到你最喜欢的搜索引擎上,搜索“前端开发工具”,你会得到一系列的结果,从文本编辑器到浏览器,再到你可以用来做笔记的笔。</p> + +<p>虽然您选择的代码编辑器肯定是一种工具选择,但本系列文章将不止于此,重点关注帮助您更有效地生成web代码的开发人员工具。</p> + +<section id="sect6"> +<article> +<section id="sect7"> +<p>从高层次来看,您可以将客户端工具放入以下三大类需要解决的问题中:</p> +</section> +</article> +</section> + +<ul> + <li><strong>安全网络 </strong>— 在代码开发期间有用的工具。</li> + <li><strong>转换 </strong>— 以某种方式转换代码的工具,例如将一种中间语言转换为浏览器可以理解的JavaScript。</li> + <li><strong>开发后阶段</strong> — 编写完代码后有用的工具,如测试和部署工具。</li> +</ul> + +<section id="sect8"> +<article> +<section id="sect9"> +<p>让我们更详细地看看每一个。</p> +</section> +</article> +</section> + +<h3 id="安全网络">安全网络</h3> + +<section id="sect10"> +<article> +<section id="sect11"> +<p>这些工具可以使您编写的代码更好一些。</p> +</section> +</article> +</section> + +<p>这一部分的工具应该特定于您自己的开发环境,尽管对于公司来说,有某种策略或可用于安装的预备配置,以便所有开发人员都使用相同的流程的情况并不少见。</p> + +<p>这包括使您的开发过程更容易生成稳定可靠的代码的任何内容。安全网络工具还应该帮助您避免错误或自动纠正错误,而不必每次都从头开始构建代码。</p> + +<section id="sect12"> +<article> +<section id="sect13"> +<p>您将发现开发人员正在使用的一些非常常见的安全网络工具类型如下。</p> +</section> +</article> +</section> + +<h4 id="Linters">Linters</h4> + +<p><strong>Linters</strong> 是检查您的代码并告诉您存在任何错误的工具,它们是什么类型的错误,以及它们出现在哪些代码行上。通常,linters不仅可以被配置为报告错误,还可以报告任何违反您的团队可能正在使用的指定样式指南的行为(例如代码使用了错误的缩进空格数,或者使用了<a href="/en-US/docs/Web/JavaScript/Reference/Template_literals">template literals</a> 而不是常规的字符串文本)。</p> + +<p><a href="https://eslint.org/">eslint</a> 业界标准的JavaScript linter是一种高度可配置的工具,用于捕获潜在的语法错误,并在代码中鼓励“最佳实践”。一些公司和项目也是这样 <a href="https://www.npmjs.com/search?q=keywords:eslintconfig">shared their eslint configs</a>。</p> + +<section id="sect14"> +<article> +<section id="sect15"> +<p>您还可以找到用于其他语言的linting工具,比如<a href="http://csslint.net/" style="">csslint</a>。</p> +</section> +</article> +</section> + +<section id="sect16"> +<article> +<section id="sect17"> +<p>同样值得一看的是 <a href="https://webhint.io/">webhint</a>, 一个可配置的,开放源码的网页链接,展示了最佳实践,包括可访问性,性能,跨浏览器兼容性 <a href="https://github.com/mdn/browser-compat-data">MDN's browser compatibility data</a>, 安全, PWAs测试等等。它可以作为 <a href="https://webhint.io/docs/user-guide/">Node.js command-line tool</a> 和 <a href="https://marketplace.visualstudio.com/items?itemName=webhint.vscode-webhint">VS Code extension</a>.</p> +</section> +</article> +</section> + +<h4 id="源代码控制">源代码控制</h4> + +<section id="sect18"> +<article> +<section id="sect19"> +<p>也称为版本控制系统(VCS),源代码控制对于备份工作和在团队中工作至关重要。典型的VCS包括拥有您要对其进行更改的代码的本地版本。然后将更改“推”到存储在某个服务器上的远程存储库中的代码的“主”版本。通常有一种方法来控制和协调对代码的“主”副本做了什么更改,以及什么时候做更改,这样开发团队就不会一直覆盖彼此的工作。</p> +</section> +</article> +</section> + +<p><a href="https://git-scm.com/">Git</a> 是现在大多数人使用的源代码控制系统。它主要通过命令行访问,但也可以通过友好的用户界面访问。使用git存储库中的代码,您可以将其推到自己的服务器实例中,或者使用托管的源代码控制网站,如<a href="https://github.com/">GitHub</a>, <a href="https://about.gitlab.com/">GitLab</a>, or <a href="https://bitbucket.org/product/features">BitBucket</a>.</p> + +<p>我们将在这个模块中使用GitHub。你可以在网站上找到更多关于它的信息<a href="/en-US/docs/Learn/Tools_and_testing/GitHub">Git and GitHub</a>.</p> + +<h4 id="代码格式化">代码格式化</h4> + +<p>代码格式化程序与linters有些关联,除了它们不是指出代码中的错误,而是根据你的样式规则,确保你的代码被正确格式化,理想情况下自动修复它们发现的错误。</p> + +<p><a href="https://prettier.io/">Prettier</a> 是一个非常流行的代码格式化程序示例,稍后我们将在模块中使用它。</p> + +<h4 id="打包工具">打包工具</h4> + +<section id="sect20"> +<article> +<section id="sect21"> +<p>这些工具让你的代码准备生产,例如,通过tree-shaking来确保只有实际使用的代码库的部分被放到最终的生产代码中,或“缩减”删除所有空格在生产代码中,使其尽可能小之前上传到服务器。</p> +</section> +</article> +</section> + +<p><a href="https://parceljs.org/">Parcel</a> 是一个特别好用的工具,都属于这个类别可以完成上面的任务,但也有助于资产包像HTML, CSS和图像文件方便的包,你可以继续部署,也为您自动添加依赖项当你试着使用它们。它甚至可以为您处理一些代码转换任务。</p> + +<p><a href="https://webpack.js.org/">Webpack</a> 是一个非常流行的代码格式化程序示例,稍后我们将在模块中使用它。</p> + +<h3 id="转换">转换</h3> + +<section id="sect22"> +<article> +<section id="sect23"> +<p>web应用程序生命周期的这个阶段通常允许您编写“未来代码”(比如最新的CSS或JavaScript特性,这些特性可能还没有得到浏览器的本地支持),或者完全使用另一种语言编写代码,比如 <a href="https://www.typescriptlang.org/">TypeScript</a>. 转换工具将为您生成与浏览器兼容的代码,以用于生产。</p> +</section> +</article> +</section> + +<section id="sect24"> +<article> +<section id="sect25"> +<p>通常web开发被认为是三种语言: <a href="/en-US/docs/Learn/HTML">HTML</a>, <a href="/en-US/docs/Learn/CSS">CSS</a>, and <a href="/en-US/docs/Learn/JavaScript">JavaScript</a>, 所有这些语言都有转换工具。转换提供了两个主要好处(还有其他好处)</p> +</section> +</article> +</section> + +<ol> + <li>能够使用最新的语言特性编写代码,并将其转换为可在日常设备上使用的代码。例如,您可能希望使用尖端的新语言特性来编JavaScript,但是您的最终产品代码仍然可以在不支持这些特性的旧浏览器上工作。例如: + <ul> + <li><a href="https://babeljs.io/">Babel</a>:一个JavaScript编译器,允许开发人员使用最前沿的JavaScript编写代码,然后Babel将其转换为老式的JavaScript,让更多的浏览器能够理解。开发人员也可以编写和发布<a href="https://babeljs.io/docs/en/plugins" style="">plugins for Babel</a><span style="">.</span></li> + <li><a href="https://postcss.org/">PostCSS</a>:和Babel做同样的事情,但是有先进的CSS特性。如果没有相同的方法使用旧的CSS特性来做一些事情,PostCSS将安装一个JavaScript填充来模拟您想要的CSS效果。 + <section id="sect26"></section> + </li> + </ul> + </li> + <li>选择用一种完全不同的语言编写代码,并将其转换为与web兼容的语言。例如: + <ul> + <li><a href="https://sass-lang.com/">Sass/SCSS</a>:这个CSS扩展允许您使用变量、嵌套规则、混合、函数和许多其他特性,其中一些特性在本地CSS中是可用的(比如变量),而另一些则不是。</li> + <li><a href="https://www.typescriptlang.org/">TypeScript</a>:TypeScript是JavaScript的一个超集,它提供了一堆额外的特性。TypeScript编译器在生成产品时将TypeScript代码转换为JavaScript。</li> + <li>框架例如 <a href="https://reactjs.org/">React</a>, <a href="https://emberjs.com/">Ember</a>, and <a href="https://vuejs.org/">Vue</a>:框架提供了许多免费的功能,并允许您通过构建在普通JavaScript之上的自定义语法来使用它们。在后台,框架的JavaScript代码努力解释这个定制语法,并将其呈现为最终的web应用程序。</li> + </ul> + </li> +</ol> + +<h3 id="开发后阶段">开发后阶段</h3> + +<section id="sect27"> +<article> +<section id="sect28"> +<p>开发后阶段工具可以确保您的软件能够访问web并继续运行。这包括部署流程、测试框架、审计工具等等。</p> +</section> +</article> +</section> + +<section id="sect29"> +<article> +<section id="sect30"> +<p>在开发过程的这个阶段,您希望与之进行最少的主动交互,这样,一旦配置完毕,它基本上是自动运行的,只有在出现错误时才弹出窗口打招呼。</p> +</section> +</article> +</section> + +<h4 id="测试工具">测试工具</h4> + +<section id="sect31"> +<article> +<section id="sect32"> +<p>它们通常采用一种工具的形式,该工具将自动对您的代码运行测试,以确保在进行进一步操作之前它是正确的(例如,当您试图将更改推送到GitHub repo时)。这可能包括linting,但也包括更复杂的过程,如单元测试,在这里运行部分代码,以确保它们按照应有的方式运行。</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect33"> + <article> + <p>框架包括编写测试<a href="https://jestjs.io/" style="">Jest</a><span style="">, </span><a href="https://mochajs.org/" style="">Mocha</a><span style="">, 和 </span><a href="https://jasmine.github.io/" style="">Jasmine</a><span style="">.</span></p> + </article> + </section> + </li> + <li> + <section id="sect34"> + <article> + <section id="sect35"> + <p>自动测试运行和通知系统包括<a href="https://travis-ci.org/" style="">Travis CI</a><span style="">, </span><a href="https://jenkins.io/" style="">Jenkins</a><span style="">, </span><a href="https://circleci.com/" style="">Circle CI</a><span style="">, 和 </span><a href="https://en.m.wikipedia.org/wiki/List_of_build_automation_software#Continuous_integration" style="">others</a><span style="">.</span></p> + </section> + </article> + </section> + </li> +</ul> + +<h4 id="配置工具">配置工具</h4> + +<p>配置系统允许您发布网站,可用于静态和动态站点,通常与测试系统一起工作。例如,典型的工具链会等待您将更改推送到远程回购,运行一些测试以查看更改是否正确,然后如果测试通过,则自动将您的应用程序部署到生产站点。</p> + +<p><a href="https://netlify.com">Netlify</a> 是目前最流行的部署工具之一,但其他包括<a href="https://vercel.com/" style="">Vercel</a><span style=""> 和</span><span style=""> </span><a href="https://pages.github.com/" style="">Github Pages</a><span style="">.</span></p> + +<h4 id="其他的">其他的</h4> + +<section id="sect36"> +<article> +<section id="sect37"> +<p>在开发后期阶段,还有许多其他可用的工具类型,包括 <a href="https://codeclimate.com/">Code Climate</a> 对于收集代码质量度量, <a href="https://webhint.io/docs/user-guide/extensions/extension-browser/">webhint browser extension</a> 用于执行跨浏览器兼容性的运行时分析和其他检查, <a href="https://probot.github.io/">Github bots</a> 提供更强大的GitHub功能, <a href="https://updown.io/">Updown</a> 提供应用程序运行时间监控等等。</p> +</section> +</article> +</section> + +<h3 id="工具种类的想法">工具种类的想法</h3> + +<section id="sect38"> +<article> +<section id="sect39"> +<p>在开发生命周期中应用不同的工具类型当然是有顺序的,但请放心,您不必在发布一个网站时将所有这些工具都准备就绪。事实上,你不需要这些。然而,在您的过程中包括这些工具将改善您自己的开发体验,并可能提高代码的总体质量。</p> +</section> +</article> +</section> + +<section id="sect40"> +<article> +<section id="sect41"> +<p>新的开发人员工具通常需要一段时间才能适应其复杂性。Webpack是最著名的工具之一,它以使用起来过于复杂而著称,但是在最新的主要版本中,它大力简化了常用的用法,因此所需的配置被减少到绝对最小。</p> +</section> +</article> +</section> + +<section id="sect42"> +<article> +<section id="sect43"> +<p>绝对没有银弹可以保证工具的成功,但是随着你经验的增加,你会发现适合你或者你的团队和他们的项目的工作流程。一旦过程中所有的扭结被平展,你的工具链应该是你可以忘记的东西,它应该只是工作。</p> +</section> +</article> +</section> + +<h2 id="如何选择并寻求特殊工具的帮助">如何选择并寻求特殊工具的帮助</h2> + +<section id="sect44"> +<article> +<section id="sect45"> +<p>大多数工具往往是独立编写和发布的,因此,尽管几乎可以肯定有可用的帮助,但它们的位置或格式永远不会相同。因此,很难找到使用工具的帮助,甚至很难选择使用什么工具。关于哪些是最好的工具的知识是有点部落式的,这意味着如果您还没有进入web社区,就很难确定到底应该使用哪些工具!这是我们编写本系列文章的原因之一,希望能够提供其他方法难以找到的第一步。</p> +</section> +</article> +</section> + +<section id="sect46"> +<article> +<section id="sect47"> +<p>您可能需要以下内容的组合</p> +</section> +</article> +</section> + +<ul> + <li> + <section id="sect48"> + <article> + <section id="sect49"> + <p>有经验的老师、导师、同学或有一定经验的同事以前解决过这类问题,并能提出建议。</p> + </section> + </article> + </section> + </li> + <li> + <section id="sect50"> + <article> + <section id="sect51"> + <p>一个有用的特定地方搜索。对前端开发人员工具的一般web搜索通常是无用的,除非您已经知道您正在搜索的工具的名称。</p> + </section> + </article> + </section> + + <ul> + <li> + <section id="sect52"> + <article> + <section id="sect53"> + <p>例如,如果您正在使用npm包管理器来管理依赖项,那么转到<a href="https://www.npmjs.com/" style="">npm homepage</a><span style=""> </span>并搜索您正在寻找的工具的类型,例如,如果您想要日期格式化实用程序,请尝试搜索“date”,如果您想要搜索通用代码格式化程序,则尝试搜索“formatter”。请注意流行度、质量和维护分数,以及软件包最近更新的时间。还可以点击工具页面,了解每个包每月有多少下载,以及它是否有好的文档,可以用来了解它是否完成了您需要它做的事情。以这些标准为基础<a href="https://www.npmjs.com/package/date-fns" style="">date-fns library</a><span style=""> </span>看起来是一个很好的日期格式化工具。在本模块的第3章中,您将看到这个工具的实际应用,并了解更多关于包管理器的信息。</p> + </section> + </article> + </section> + </li> + <li>如果您正在寻找将工具功能集成到代码编辑器中的插件,请查看代码编辑器的“插件/扩展名”页面-请参阅 <a href="https://atom.io/packages">Atom packages</a> and <a href="https://marketplace.visualstudio.com/VSCode">VSCode extensions</a>,为例。看一看首页上的特色扩展,然后再次尝试搜索你想要的扩展类型(或者工具名称,例如在VSCode扩展页面上搜索“eslint”)。当你得到结果的时候,看看这个扩展有多少颗星或者下载了多少,作为它质量的一个指标。 + <section id="sect54"></section> + </li> + </ul> + </li> + <li> + <section id="sect55"> + <article> + <section id="sect56"> + <p>例如,与开发相关的论坛,询问关于使用什么工具的问题<a href="https://discourse.mozilla.org/c/mdn/learn" style="">MDN Learn Discourse</a><span style="">, or </span><a href="https://stackoverflow.com/" style="">Stack Overflow</a><span style="">.</span></p> + </section> + </article> + </section> + </li> +</ul> + +<section id="sect57"> +<article> +<section id="sect58"> +<p>当您选择要使用的工具时,第一个端口应该是工具项目主页。这可能是一个完整的网站,也可能是代码库中的一个readme文档。例如 <a href="https://date-fns.org/docs/Getting-Started">date-fns docs</a>就很好、很完整、很容易理解。然而,有些文档可能相当技术性和学术性,并不适合您的学习需求。</p> +</section> +</article> +</section> + +<section id="sect59"> +<article> +<section id="sect60"> +<p>相反,您可能希望找到一些关于如何开始使用特定类型的工具的专门教程。好的起点是搜索网站喜欢<a href="https://css-tricks.com/" style="">CSS Tricks</a><span style="">, </span><a href="https://dev.to/" style="">Dev</a><span style="">, </span><a href="https://www.freecodecamp.org/" style="">freeCodeCamp</a><span style="">, and </span><a href="https://www.smashingmagazine.com/" style="">Smashing Magazine</a><span style="">, </span>因为它们是为web开发行业量身定做的。</p> +</section> +</article> +</section> + +<section id="sect61"> +<article> +<section id="sect62"> +<p>同样,在搜索适合自己的工具时,您可能会使用几种不同的工具,试用它们,看看它们是否有意义,是否得到良好的支持,并执行您希望它们执行的任务。这很好,这对学习很有好处,而且随着你获得更多经验,道路会变得更平坦。</p> +</section> +</article> +</section> + +<h2 id="总结">总结</h2> + +<section id="sect63"> +<article> +<section id="sect64"> +<p>以上是我们对客户端web工具主题的简要介绍的最后一部分。接下来,我们将为您提供一个关于命令行的速成课程,许多工具都是从命令行调用的。我们将看一看命令行可以做什么,然后尝试安装和使用我们的第一个工具。</p> +</section> +</article> +</section> + +<p>{{NextMenu("Learn/Tools_and_testing/Understanding_client-side_tools/Command_line", "Learn/Tools_and_testing/Understanding_client-side_tools")}}</p> + +<h2 id="In_this_module">In this module</h2> + +<ul> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Overview">Client-side tooling overview</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Command_line">Command line crash course</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Package_management">Package management basics</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Introducing_complete_toolchain">Introducing a complete toolchain</a></li> + <li><a href="/en-US/docs/Learn/Tools_and_testing/Understanding_client-side_tools/Deployment">Deploying our app</a></li> +</ul> |