就业培训     下载中心     Wiki     联络
登录   注册

Log
  1. 首页
  2. 学习 Web 开发
  3. Tools and testing
  4. 理解客户端侧 JavaScript 框架
  5. Componentizing our Svelte app

内容表

  • Code along with us
  • Breaking the app into components
  • Extracting our filter component
  • Sharing data between components: passing a handler as a prop
  • Easier two-way data binding with the bind directive
  • Creating our Todo component
  • Sharing data between components: props-down, events-up pattern
  • Updating todos
  • The code so far
  • 摘要
  • In this module

Componentizing our Svelte app

  • 上一
  • Overview: Client-side JavaScript frameworks
  • 下一

In the last article we started developing our Todo list app. The central objective of this article is to look at how to break our app into manageable components and share information between them. We'll componentize our app, then add more functionality to allow users to update existing components.

Prerequisites: At minimum, it is recommended that you are familiar with the core HTML , CSS ,和 JavaScript languages, and have knowledge of the terminal/command line .

You'll need a terminal with node + npm installed to compile and build your app.

Objective: To learn how to break our app into components and share information among them.

Code along with us

Git

Clone the github repo (if you haven't already done it) with:

git clone https://github.com/opensas/mdn-svelte-tutorial.git

								

Then to get to the current app state, run

cd mdn-svelte-tutorial/04-componentizing-our-app

								

Or directly download the folder's content:

npx degit opensas/mdn-svelte-tutorial/04-componentizing-our-app

								

Remember to run npm install && npm run dev to start your app in development mode.

REPL

To code along with us using the REPL, start at

https://svelte.dev/repl/99b9eb228b404a2f8c8959b22c0a40d3?version=3.23.2

Breaking the app into components

In Svelte, an application is composed from one or more components. A component is a reusable, self-contained block of code that encapsulates HTML, CSS and JavaScript that belong together, written into a .svelte file. Components can be big or small, but they are usually clearly defined: the most effective components serve a single, obvious purpose.

The benefits of defining components are comparable to the more general best practice of organizing your code into manageable pieces. It will help you understand how they relate to each other, it will promote reuse, and it will make your code easier to reason about, maintain, and extend.

But how do you know what should be split into its own component?

There are no hard rules for this. Some people prefer an intuitive approach and start looking at the markup and drawing boxes around every component and subcomponent that seems to have its own logic.

Other people apply the same techniques used for deciding if you should create a new function or object. One such technique is the single responsibility principle — that is, a component should ideally only do one thing. If it ends up growing, it should be split into smaller subcomponents.

Both approaches should complement each other, and help you decide how to better organize your components.

Eventually, we will split up our app into the following components:

  • Alert.svelte : A general notification box for communicating actions that have occurred.
  • NewTodo.svelte : The text input and button that allow you to enter a new todo item.
  • FilterButton.svelte : 所有 , Active ,和 Completed buttons that allow you to apply filters to the displayed todo items.
  • TodosStatus.svelte : The "x out of y items completed" heading.
  • Todo.svelte : An individual todo item. Each visible todo item will be displayed in a separate copy of this component.
  • MoreActions.svelte : Check All and Remove Completed buttons at the bottom of the UI that allow you to perform mass actions on the todo items.

graphical representation of the list of components in our app

In this article we will concentrate on creating the FilterButton and Todo components; we'll get to the others in future articles.

Let's get started.

注意: In the process of creating our first couple of components, we will also learn different techniques to communicate between components, and the pros and cons of each.

Extracting our filter component

We'll begin by creating our FilterButton.svelte .

  1. First of all, create a new file — components/FilterButton.svelte .
  2. Inside this file we will declare a filter prop, and then copy the relevant markup over to it from Todos.svelte . Add the following content into the file:
    <script>
      export let filter = 'all'
    </script>
    <div class="filters btn-group stack-exception">
      <button class="btn toggle-btn" class:btn__primary={filter === 'all'} aria-pressed={filter === 'all'} on:click={()=> filter = 'all'} >
        <span class="visually-hidden">Show</span>
        <span>All</span>
        <span class="visually-hidden">tasks</span>
      </button>
      <button class="btn toggle-btn" class:btn__primary={filter === 'active'} aria-pressed={filter === 'active'} on:click={()=> filter = 'active'} >
        <span class="visually-hidden">Show</span>
        <span>Active</span>
        <span class="visually-hidden">tasks</span>
      </button>
      <button class="btn toggle-btn" class:btn__primary={filter === 'completed'} aria-pressed={filter === 'completed'} on:click={()=> filter = 'completed'} >
        <span class="visually-hidden">Show</span>
        <span>Completed</span>
        <span class="visually-hidden">tasks</span>
      </button>
    </div>
    
    										
  3. Back in our Todos.svelte component, we want to make use of our FilterButton component. First of all, we need to import it — add the following line at the top of the Todos.svelte <script> section:
    import FilterButton from './FilterButton.svelte'
    
    										
  4. Now, replace the 过滤 <div> with a call to the FilterButton component, which takes the current filter as a prop — the below line is all you need:
    <FilterButton {filter} />
    
    										

注意: Remember that when the HTML attribute name and variable matches, they can be replaced with {variable} , that's why we could replace <FilterButton filter={filter} /> with <FilterButton {filter} /> .

So far so good! Let's try out the app now. You'll notice that when you click on the filter buttons, they are selected and the style updates appropriately. But! We have a problem — the todos aren't filtered. That's because the filter variable flows down from the Todos component to the FilterButton component through the prop, but changes occurring in the FilterButton component don't flow back up to its parent — the data binding is one-way by default. Let's look at a way to solve this.

Sharing data between components: passing a handler as a prop

One way to let child components notify their parents of any changes is to pass a handler as a prop. The child component will execute the handler, passing the needed information as a parameter, and the handler will modify the parent's state.

In our case, the FilterButton component will receive an onclick handler from its parent. Whenever the user clicks on any filter button, the child will call the onclick handler, passing the selected filter as a parameter, back up to its parent.

We will just declare the onclick prop assigning a dummy handler to prevent errors, like this:

export let onclick = (clicked) => {}

								

And we'll declare the following reactive statement — $: onclick(filter) — to call the onclick handler whenever the filter variable is updated.

  1. <script> section of our FilterButton component should end up looking like this — update it now:
    <script>
      export let filter = 'all'
      export let onclick = (clicked) => {}
      $: onclick(filter)
    </script>
    
    										
  2. Now when we call FilterButton inside Todos.svelte we'll need to specify the handler. Update it like this:
    <FilterButton {filter} onclick={ (clicked) => filter = clicked }/>
    
    										

When any filter button is clicked, we just update the filter variable with the new filter. Now our FilterButton component will work again.

Easier two-way data binding with the bind directive

In the previous example we realized that our FilterButton component wasn't working because our application state was flowing down from parent to child through the filter prop — but it wasn't going back up. So we added an onclick prop to let the child component communicate the new filter value to its parent.

It works OK, but Svelte provides us an easier and more straightforward way to achieve two-way data binding. Data ordinarily flows down from parent to child using props. If we want it to also flow the other way — from child to parent — we can use the bind: directive .

使用 bind , we will tell Svelte that any changes made to the filter prop in the FilterButton component should propagate back up to the parent component, Todos . That is, we will bind the filter variable's value in the parent to its value in the child.

  1. 在 Todos.svelte , update the call to the FilterButton component as follows:
    <FilterButton bind:filter={filter} />
    
    										
    As usual, Svelte provides us with a nice shorthand — bind:value={value} 相当于 bind:value . So in the above example you could just write <FilterButton bind:filter /> .
  2. The child component can now modify the value of the parent's filter variable, so we no longer need the onclick prop. Modify your FilterButton <script> 像这样:
    <script>
      export let filter = 'all'
    </script>
    
    										
  3. Try your app again, and you should still see your filters working correctly.

Creating our Todo component

Now we will create a Todo component to encapsulate each individual todo — including the checkbox and some editing logic so you can change an existing todo.

我们的 Todo component will receive a single todo object as a prop. Let's declare the todo prop and move the code from the Todos component. Just for now, we'll replace the call to removeTodo with an alert. We'll add that functionality back in later on.

  1. Create a new component file — components/Todo.svelte .
  2. Put the following contents inside this file:
    <script>
      export let todo
    </script>
    <div class="stack-small">
      <div class="c-cb">
        <input type="checkbox" id="todo-{todo.id}"
          on:click={() => todo.completed = !todo.completed}
          checked={todo.completed}
        />
        <label for="todo-{todo.id}" class="todo-label">{todo.name}</label>
      </div>
      <div class="btn-group">
        <button type="button" class="btn">
          Edit <span class="visually-hidden">{todo.name}</span>
        </button>
        <button type="button" class="btn btn__danger" on:click={() => alert('not implemented')}>
          Delete <span class="visually-hidden">{todo.name}</span>
        </button>
      </div>
    </div>
    
    										
  3. Now we need to import our Todo component into Todos.svelte . Go to this file now, and add the following import statement below your previous one:
    import Todo from './Todo.svelte'
    
    										
  4. Next, we need to update our {#each} block to include a <Todo> component for each todo, rather than the code that has been moved out to Todo.svelte . We are also passing the current todo object into the component as a prop. 更新 {#each} block inside Todos.svelte 像这样:
    <ul role="list" class="todo-list stack-large" aria-labelledby="list-heading">
      {#each filterTodos(filter, todos) as todo (todo.id)}
        <li class="todo">
          <Todo {todo} />
        </li>
      {:else}
        <li>Nothing to do here!</li>
      {/each}
    </ul>
    
    										

The list of todos is displayed on the page, and the checkboxes should work (try checking/unchecking a couple, and then observing that the filters still work as expected), but our "x out of y items completed" status heading will no longer update accordingly. That's because our Todo component is receiving the todo via the prop, but it's not sending any information back to its parent. We'll fix this later on.

Sharing data between components: props-down, events-up pattern

bind directive is pretty straightforward and allows you to share data between a parent and child component with minimal fuss. However, when your application grows larger and more complex it can easily get difficult to keep track of all your bound values. A different approach is the "props-down, events-up" communication pattern.

Basically, this pattern relies on child components receiving data from their parents via props and parent components updating their state by handling events emitted from child components. So props flow down from parent to child and events bubble up from child to parent. This pattern establishes a two-way flow of information, which is predictable and easier to reason about.

Let's look at how to emit our own events to re-implement the missing 删除 button functionality.

To create custom events we'll use the createEventDispatcher utility. This will return a dispatch() function that will allow us to emit custom events. When you dispatch an event you have to pass the name of the event and, optionally, an object with additional information that you want to pass to every listener. This additional data will be available on the detail property of the event object.

注意: Custom events in Svelte share the same API as regular DOM events. Moreover, you can bubble up an event to your parent component by specifying on:event without any handler.

We'll edit our Todo component to emit a remove event, passing the todo being removed as additional information.

  1. First of all, add the following lines to the top of the Todo component's <script> section:
    import { createEventDispatcher } from 'svelte'
    const dispatch = createEventDispatcher()
    
    										
  2. Now update the 删除 button in the markup section of the same file to look like so:
    <button type="button" class="btn btn__danger" on:click={() => dispatch('remove', todo)}>
      Delete <span class="visually-hidden">{todo.name}</span>
    </button>
    
    										
    采用 dispatch('remove', todo) we are emitting a remove event, and passing as additional data the todo being deleted. The handler will be called with an event object available, with the additional data available in the event.detail 特性。
  3. Now we have to listen to that event from inside Todos.svelte and act accordingly. Go back to this file and update your <Todo> component call like so:
    <Todo {todo} on:remove={e => removeTodo(e.detail)} />
    
    										
    Our handler receives the e parameter (the event object), which as described before holds the todo being deleted in the detail 特性。
  4. At this point, if you try out your app again, you should see that the 删除 functionality now works again! So our custom event has worked as we hoped. In addition, the remove event listener is sending the data change back up to the parent, so our "x out of y items completed" status heading will now update appropriately when todos are deleted.

Now we'll take care of the update event, so that our parent component can get notified of any modified todo.

Updating todos

We still have to implement functionality to allow us to edit existing todos. We'll have to include an editing mode in the Todo component. When entering editing mode we'll show an <input> field to allow us to edit the current todo name, with two buttons to confirm or cancel our changes.

Handling the events

  1. We'll need one variable to track whether we are in editing mode and another to store the name of the task being updated. Add the following variable definitions at the bottom of the <script> 章节的 Todo component:
    let editing = false                     // track editing mode
    let name = todo.name                    // hold the name of the todo being edited
    
    										
  2. We have to decide what events our Todo component will emit:
    • We could emit different events for the status toggle and editing of the name. (e.g. updateTodoStatus and updateTodoName ).
    • Or we could take a more generic approach and emit a single update event for both operations.
    We will take the second approach so we can demonstrate a different technique. The advantage of this approach is that later we can add more fields to the todos and still handle all updates with the same event. Let's create an update() function that will receive the changes and will emit an update event with the modified todo. Add the following, again to the bottom of the <script> section:
    function update(updatedTodo) {
      todo = { ...todo, ...updatedTodo }    // applies modifications to todo
      dispatch('update', todo)              // emit update event
    }
    
    										
    Here we are using the spread syntax to return the original todo with the modifications applied to it.
  3. Next we'll create different functions to handle each user action. When the Todo is in editing mode, the user can save or cancel the changes. When it's not in editing mode, the user can delete the todo, edit it, or toggle its status between completed and active. Add the following set of functions below your previous function to handle these actions:
    function onCancel() {
      name = todo.name                      // restores name to its initial value and
      editing = false                       // and exit editing mode
    }
    function onSave() {
      update({ name: name })                // updates todo name
      editing = false                       // and exit editing mode
    }
    function onRemove() {
      dispatch('remove', todo)              // emit remove event
    }
    function onEdit() {
      editing = true                        // enter editing mode
    }
    function onToggle() {
      update({ completed: !todo.completed}) // updates todo status
    }
    
    										

Updating the markup

Now we need to update our Todo component's markup to call the above functions when the appropriate actions are taken.

To handle the editing mode we are using the editing variable, which is a boolean. When it's true , it should display the <input> field for editing the todo name, and the 取消 and 保存 buttons. When it's not in editing mode it will display the checkbox, the todo name and the buttons to edit and delete the todo.

To achieve this we will use an if block 。 if block conditionally renders some markup. Take into account that it won't just show or hide the markup based on the condition — it will dynamically add and remove the elements from the DOM, depending on the condition.

当 editing is true , for example, Svelte will show the update form; when it's false , it will remove it from the DOM and add in the checkbox. Thanks to Svelte reactivity, assigning the value of the editing variable will be enough to display the correct HTML elements.

The following gives you an idea of what the basic if block structure looks like:

<div class="stack-small">
{#if editing}
  <!-- markup for editing todo: label, input text, Cancel and Save Button -->
{:else}
  <!-- markup for displaying todo: checkbox, label, Edit and Delete Button -->
{/if}
</div>

								

The non-editing section — that is, the {:else} part (lower half) of the if block — will be very similar to the one we had in our Todos component. The only difference is that we are calling onToggle() , onEdit() ,和 onRemove() , depending on the user action.

{:else}
  <div class="c-cb">
    <input type="checkbox" id="todo-{todo.id}"
      on:click={onToggle} checked={todo.completed}
    >
    <label for="todo-{todo.id}" class="todo-label">{todo.name}</label>
  </div>
  <div class="btn-group">
    <button type="button" class="btn" on:click={onEdit}>
      Edit<span class="visually-hidden"> {todo.name}</span>
    </button>
    <button type="button" class="btn btn__danger" on:click={onRemove}>
      Delete<span class="visually-hidden"> {todo.name}</span>
    </button>
  </div>
{/if}
</div>

								

It is worth noting that:

  • When the user presses the 编辑 button we execute onEdit() , which just sets the editing 变量到 true .
  • When the user clicks on the checkbox we call the onToggle() function, which executes update() , passing an object with the new completed value as a parameter.
  • update() function emits the update event, passing as additional information a copy of the original todo with the changes applied.
  • 最后, onRemove() function emits the remove event, passing the todo to be deleted as additional data.

The editing UI (the upper half) will contain an <input> field and two buttons to cancel or save the changes:

<div class="stack-small">
{#if editing}
  <form on:submit|preventDefault={onSave} class="stack-small" on:keydown={e => e.key === 'Escape' && onCancel()}>
    <div class="form-group">
      <label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label>
      <input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" />
    </div>
    <div class="btn-group">
      <button class="btn todo-cancel" on:click={onCancel} type="button">
        Cancel<span class="visually-hidden">renaming {todo.name}</span>
        </button>
      <button class="btn btn__primary todo-edit" type="submit" disabled={!name}>
        Save<span class="visually-hidden">new name for {todo.name}</span>
      </button>
    </div>
  </form>
{:else}
[...]

								

When the user presses the 编辑 button, the editing 变量会被设为 true , and Svelte will remove the markup in the {:else} part of the DOM and replace it with the markup in the {#if...} 章节。

<input> 's value property will be bound to the 名称 variable, and the buttons to cancel and save the changes call onCancel() and onSave() respectively (we added those functions earlier):

  • 当 onCancel() is invoked, 名称 is restored to its original value (when passed in as a prop) and we exit editing mode (by setting editing to false ).
  • 当 onSave() in invoked, we run the update() function — passing it the modified 名称 — and exit editing mode.

We also disable the 保存 button when the <input> is empty, using the disabled={!name} attribute, and allow the user to cancel the edit using the Escape key, like this:

on:keydown={e => e.key === 'Escape' && onCancel()}.

								

We also use todo.id to create unique ids for the new input controls and labels.

  1. The complete updated markup of our Todo component looks like the following. Update yours now:
    <div class="stack-small">
    {#if editing}
      <!-- markup for editing todo: label, input text, Cancel and Save Button -->
      <form on:submit|preventDefault={onSave} class="stack-small" on:keydown={e => e.key === 'Escape' && onCancel()}>
        <div class="form-group">
          <label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label>
          <input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" />
        </div>
        <div class="btn-group">
          <button class="btn todo-cancel" on:click={onCancel} type="button">
            Cancel<span class="visually-hidden">renaming {todo.name}</span>
            </button>
          <button class="btn btn__primary todo-edit" type="submit" disabled={!name}>
            Save<span class="visually-hidden">new name for {todo.name}</span>
          </button>
        </div>
      </form>
    {:else}
      <!-- markup for displaying todo: checkbox, label, Edit and Delete Button -->
      <div class="c-cb">
        <input type="checkbox" id="todo-{todo.id}"
          on:click={onToggle} checked={todo.completed}
        >
        <label for="todo-{todo.id}" class="todo-label">{todo.name}</label>
      </div>
      <div class="btn-group">
        <button type="button" class="btn" on:click={onEdit}>
          Edit<span class="visually-hidden"> {todo.name}</span>
        </button>
        <button type="button" class="btn btn__danger" on:click={onRemove}>
          Delete<span class="visually-hidden"> {todo.name}</span>
        </button>
      </div>
    {/if}
    </div>
    
    										

    注意: We could further split this into two different components, one for editing the todo and the other for displaying it. In the end, it boils down to how comfortable you feel dealing with this level of complexity in a single component. You should also consider whether splitting it further would enable reusing this component in a different context.

  2. To get the update functionality working, we have to handle the update event from the Todos component. In its <script> section, add this handler:
    function updateTodo(todo) {
      const i = todos.findIndex(t => t.id === todo.id)
      todos[i] = { ...todos[i], ...todo }
    }
    
    										
    We find the todo by id in our todos array, and update its content using spread syntax. In this case we could have also just used todos[i] = todo , but this implementation is more bullet-proof, allowing the Todo component to return only the updated parts of the todo.
  3. Next we have to listen for the update event on our <Todo> component call, and run our updateTodo() function when this occurs to change the 名称 and completed status. Update your <Todo> call like this:
    {#each filterTodos(filter, todos) as todo (todo.id)}
      <li class="todo">
        <Todo {todo}
          on:update={e => updateTodo(e.detail)}
          on:remove={e => removeTodo(e.detail)}
        />
      </li>
    
    										
  4. Try your app again, and you should see that you can delete, add, edit, cancel editing of, and toggle completion status of todos! And our "x out of y items completed" status heading will now update appropriately when todos are completed.

As you can see, it's easy to implement the "props-down, events-up" pattern in Svelte. Nevertheless, for simple components bind can be a good choice; Svelte will let you choose.

注意: Svelte provides more advanced mechanisms to share information among components: the Context API and Stores . The Context API provides a mechanism for components and their descendants to "talk" to each other without passing around data and functions as props, or dispatching lots of events. Stores allows you to share reactive data among components that are not hierarchically related. We will look at Stores later on in the series.

The code so far

Git

To see the state of the code as it should be at the end of this article, access your copy of our repo like this:

cd mdn-svelte-tutorial/05-advanced-concepts

								

Or directly download the folder's content:

npx degit opensas/mdn-svelte-tutorial/05-advanced-concepts

								

Remember to run npm install && npm run dev to start your app in development mode.

REPL

To see the current state of the code in a REPL, visit:

https://svelte.dev/repl/76cc90c43a37452e8c7f70521f88b698?version=3.23.2

摘要

Now we have all of our app's required functionality in place. We can display, add, edit and delete todos, mark them as completed, and filter by status.

In this article, we covered the following topics:

  • Extracting functionality to a new component.
  • Passing information from child to parent using a handler received as a prop.
  • Passing information from child to parent using the bind 指令。
  • Conditionally rendering blocks of markup using the if 块。
  • Implementing the "props-down, events-up" communication pattern.
  • Creating and listening to custom events.

In the next article we will continue componentizing our app and look at some advanced techniques for working with the DOM.

  • 上一
  • Overview: Client-side JavaScript frameworks
  • 下一

In this module

  • Introduction to client-side frameworks
  • Framework main features
  • React
    • Getting started with React
    • Beginning our React todo list
    • Componentizing our React app
    • React interactivity: Events and state
    • React interactivity: Editing, filtering, conditional rendering
    • Accessibility in React
    • React resources
  • Ember
    • Getting started with Ember
    • Ember app structure and componentization
    • Ember interactivity: Events, classes and state
    • Ember Interactivity: Footer functionality, conditional rendering
    • Routing in Ember
    • Ember resources and troubleshooting
  • Vue
    • Getting started with Vue
    • Creating our first Vue component
    • Rendering a list of Vue components
    • Adding a new todo form: Vue events, methods, and models
    • Styling Vue components with CSS
    • Using Vue computed properties
    • Vue conditional rendering: editing existing todos
    • Focus management with Vue refs
    • Vue resources
  • Svelte
    • Getting started with Svelte
    • Starting our Svelte Todo list app
    • Dynamic behavior in Svelte: working with variables and props
    • Componentizing our Svelte app
    • Advanced Svelte: Reactivity, lifecycle, accessibility
    • Working with Svelte stores
    • TypeScript support in Svelte
    • Deployment and next steps
  • Angular
    • Getting started with Angular
    • Beginning our Angular todo list app
    • Styling our Angular app
    • Creating an item component
    • Filtering our to-do items
    • Building Angular applications and further resources

发现此页面有问题吗?

  • 编辑在 GitHub
  • 源在 GitHub
  • Report a problem with this content on GitHub
  • 想要自己修复问题吗?见 我们的贡献指南 .

最后修改: Oct 13, 2021 , 由 MDN 贡献者

相关话题

  1. Complete beginners start here!
  2. Web 快速入门
    1. Getting started with the Web overview
    2. 安装基本软件
    3. What will your website look like?
    4. 处理文件
    5. HTML 基础
    6. CSS 基础
    7. JavaScript 基础
    8. 发布您的网站
    9. How the Web works
  3. HTML — Structuring the Web
  4. HTML 介绍
    1. Introduction to HTML overview
    2. Getting started with HTML
    3. What's in the head? Metadata in HTML
    4. HTML text fundamentals
    5. Creating hyperlinks
    6. Advanced text formatting
    7. Document and website structure
    8. Debugging HTML
    9. Assessment: Marking up a letter
    10. Assessment: Structuring a page of content
  5. 多媒体和嵌入
    1. Multimedia and embedding overview
    2. Images in HTML
    3. Video and audio content
    4. From object to iframe — other embedding technologies
    5. Adding vector graphics to the Web
    6. Responsive images
    7. Assessment: Mozilla splash page
  6. HTML 表格
    1. HTML tables overview
    2. HTML table basics
    3. HTML Table advanced features and accessibility
    4. Assessment: Structuring planet data
  7. CSS — Styling the Web
  8. CSS 第一步
    1. CSS first steps overview
    2. What is CSS?
    3. Getting started with CSS
    4. How CSS is structured
    5. How CSS works
    6. Using your new knowledge
  9. CSS 构建块
    1. CSS building blocks overview
    2. Cascade and inheritance
    3. CSS 选择器
    4. The box model
    5. Backgrounds and borders
    6. Handling different text directions
    7. Overflowing content
    8. Values and units
    9. Sizing items in CSS
    10. Images, media, and form elements
    11. Styling tables
    12. Debugging CSS
    13. Organizing your CSS
  10. 样式化文本
    1. Styling text overview
    2. Fundamental text and font styling
    3. Styling lists
    4. Styling links
    5. Web fonts
    6. Assessment: Typesetting a community school homepage
  11. CSS 布局
    1. CSS layout overview
    2. Introduction to CSS layout
    3. Normal Flow
    4. Flexbox
    5. Grids
    6. Floats
    7. 位置
    8. Multiple-column Layout
    9. Responsive design
    10. Beginner's guide to media queries
    11. Legacy Layout Methods
    12. Supporting Older Browsers
    13. Fundamental Layout Comprehension
  12. JavaScript — Dynamic client-side scripting
  13. JavaScript 第一步
    1. JavaScript first steps overview
    2. What is JavaScript?
    3. A first splash into JavaScript
    4. What went wrong? Troubleshooting JavaScript
    5. Storing the information you need — Variables
    6. Basic math in JavaScript — Numbers and operators
    7. Handling text — Strings in JavaScript
    8. Useful string methods
    9. 数组
    10. Assessment: Silly story generator
  14. JavaScript 构建块
    1. JavaScript building blocks overview
    2. Making decisions in your code — Conditionals
    3. Looping code
    4. Functions — Reusable blocks of code
    5. Build your own function
    6. Function return values
    7. 事件介绍
    8. Assessment: Image gallery
  15. 引入 JavaScript 对象
    1. Introducing JavaScript objects overview
    2. Object basics
    3. 对象原型
    4. Object-oriented programming concepts
    5. Classes in JavaScript
    6. Working with JSON data
    7. Object building practice
    8. Assessment: Adding features to our bouncing balls demo
  16. 异步 JavaScript
    1. Asynchronous JavaScript overview
    2. General asynchronous programming concepts
    3. Introducing asynchronous JavaScript
    4. Cooperative asynchronous Java​Script: Timeouts and intervals
    5. Graceful asynchronous programming with Promises
    6. Making asynchronous programming easier with async and await
    7. Choosing the right approach
  17. 客户端侧 Web API
    1. 客户端侧 Web API
    2. Introduction to web APIs
    3. Manipulating documents
    4. Fetching data from the server
    5. Third party APIs
    6. Drawing graphics
    7. Video and audio APIs
    8. Client-side storage
  18. Web forms — Working with user data
  19. Core forms learning pathway
    1. Web forms overview
    2. Your first form
    3. How to structure a web form
    4. Basic native form controls
    5. The HTML5 input types
    6. Other form controls
    7. Styling web forms
    8. Advanced form styling
    9. UI pseudo-classes
    10. Client-side form validation
    11. Sending form data
  20. Advanced forms articles
    1. How to build custom form controls
    2. Sending forms through JavaScript
    3. CSS property compatibility table for form controls
  21. Accessibility — Make the web usable by everyone
  22. Accessibility guides
    1. Accessibility overview
    2. What is accessibility?
    3. HTML: A good basis for accessibility
    4. CSS and JavaScript accessibility best practices
    5. WAI-ARIA basics
    6. Accessible multimedia
    7. Mobile accessibility
  23. Accessibility assessment
    1. Assessment: Accessibility troubleshooting
  24. Tools and testing
  25. Client-side web development tools
    1. Client-side web development tools index
    2. Client-side tooling overview
    3. Command line crash course
    4. Package management basics
    5. Introducing a complete toolchain
    6. Deploying our app
  26. Introduction to client-side frameworks
    1. Client-side frameworks overview
    2. Framework main features
  27. React
    1. Getting started with React
    2. Beginning our React todo list
    3. Componentizing our React app
    4. React interactivity: Events and state
    5. React interactivity: Editing, filtering, conditional rendering
    6. Accessibility in React
    7. React resources
  28. Ember
    1. Getting started with Ember
    2. Ember app structure and componentization
    3. Ember interactivity: Events, classes and state
    4. Ember Interactivity: Footer functionality, conditional rendering
    5. Routing in Ember
    6. Ember resources and troubleshooting
  29. Vue
    1. Getting started with Vue
    2. Creating our first Vue component
    3. Rendering a list of Vue components
    4. Adding a new todo form: Vue events, methods, and models
    5. Styling Vue components with CSS
    6. Using Vue computed properties
    7. Vue conditional rendering: editing existing todos
    8. Focus management with Vue refs
    9. Vue resources
  30. Svelte
    1. Getting started with Svelte
    2. Starting our Svelte Todo list app
    3. Dynamic behavior in Svelte: working with variables and props
    4. Componentizing our Svelte app
    5. Advanced Svelte: Reactivity, lifecycle, accessibility
    6. Working with Svelte stores
    7. TypeScript support in Svelte
    8. Deployment and next steps
  31. Angular
    1. Getting started with Angular
    2. Beginning our Angular todo list app
    3. Styling our Angular app
    4. Creating an item component
    5. Filtering our to-do items
    6. Building Angular applications and further resources
  32. Git and GitHub
    1. Git and GitHub overview
    2. Hello World
    3. Git Handbook
    4. Forking Projects
    5. About pull requests
    6. Mastering Issues
  33. Cross browser testing
    1. Cross browser testing overview
    2. Introduction to cross browser testing
    3. Strategies for carrying out testing
    4. Handling common HTML and CSS problems
    5. Handling common JavaScript problems
    6. Handling common accessibility problems
    7. Implementing feature detection
    8. Introduction to automated testing
    9. Setting up your own test automation environment
  34. Server-side website programming
  35. 第一步
    1. First steps overview
    2. Introduction to the server-side
    3. Client-Server overview
    4. Server-side web frameworks
    5. Website security
  36. Django Web 框架 (Python)
    1. Django web framework (Python) overview
    2. 介绍
    3. 设置开发环境
    4. Tutorial: The Local Library website
    5. Tutorial Part 2: Creating a skeleton website
    6. Tutorial Part 3: Using models
    7. Tutorial Part 4: Django admin site
    8. Tutorial Part 5: Creating our home page
    9. Tutorial Part 6: Generic list and detail views
    10. Tutorial Part 7: Sessions framework
    11. Tutorial Part 8: User authentication and permissions
    12. Tutorial Part 9: Working with forms
    13. Tutorial Part 10: Testing a Django web application
    14. Tutorial Part 11: Deploying Django to production
    15. Web application security
    16. Assessment: DIY mini blog
  37. Express Web Framework (node.js/JavaScript)
    1. Express Web Framework (Node.js/JavaScript) overview
    2. Express/Node introduction
    3. Setting up a Node (Express) development environment
    4. Express tutorial: The Local Library website
    5. Express Tutorial Part 2: Creating a skeleton website
    6. Express Tutorial Part 3: Using a database (with Mongoose)
    7. Express Tutorial Part 4: Routes and controllers
    8. Express Tutorial Part 5: Displaying library data
    9. Express Tutorial Part 6: Working with forms
    10. Express Tutorial Part 7: Deploying to production
  38. Further resources
  39. Common questions
    1. HTML questions
    2. CSS questions
    3. JavaScript questions
    4. Web mechanics
    5. Tools and setup
    6. Design and accessibility
  • Web 技术
  • Learn Web Development
  • About MDN
  • Feedback
  • 关于
  • MDN Web Docs Store
  • 联络我们
  • Firefox

MDN

  • MDN on Twitter
  • MDN on Github

Mozilla

  • Mozilla on Twitter
  • Mozilla on Instagram

© 2005- 2022 Mozilla and individual contributors. Content is available under these licenses .

  • Terms
  • Privacy
  • Cookie