コンポーネントを組み合わせる

複雑な処理を分かりやすくして、コードを適切な単位に分割する一般的な方法はコンポーネントに分割してそれらを組み合わせることです。 これはシンプルで小さいコンポーネントを組み合わせて大きくて複雑なコンポーネントを構築することを意味します。 Imagine you've been tasked with implementing a screen of UI:

You can probably identify the areas which will involve some complexity to implement. Chances are, those could be components.

By isolating the complexity into specific components, you make the job much simpler, and you can then compose these components together to create the overall design.

For example, the fairly simple screenshot above involves a number of possible components: a top bar, a menu button, a drawer with menu items for navigating the current section; and a main content area. Each of these could be represented by a component. A complex component, like a drawer with a navigation menu, might be broken into many smaller components: the drawer itself, a button to open and close the drawer, the menu, individual menu items.

Lit lets you compose by adding elements to your template—whether those are built-in HTML elements or custom elements.

render() {
  return html`
    <top-bar>
      <icon-button icon="menu" slot="nav-button"></icon-button>
      <span slot="title">Fuzzy</span>
    </top-bar>
    `;
}

良いコンポーネントとは

When deciding how to break up functionality, there are several things that help identify when to make a new component. A piece of UI may be a good candidate for a component if one or more of the following applies:

Reusable controls like buttons, checkboxes, and input fields can make great components. But more complex UI pieces like drawers and carousels are also great candidates for componentization.

Passing data up and down the tree

When exchanging data with subcomponents, the general rule is to follow the model of the DOM: properties down, events up.

A few implications of this model:

Consider a menu component that includes a set of menu items and exposes items and selectedItem properties as part of its public API. Its DOM structure might look like this:

When the user selects an item, the my-menu element should update its selectedItem property. It should also fire an event to notify any owning component that the selection has changed. The complete sequence would be something like this:

For more information on dispatching and listening for events, see Events.

Passing data across the tree

Properties down and events up is a good rule to start with. But what if you need to exchange data between two components that don't have a direct descendant relationship? For example, two components that are siblings in the shadow tree?

One solution to this problem is to use the mediator pattern. In the mediator pattern, peer components don't communicate with each other directly. Instead, interactions are mediated by a third party.

A simple way to implement the mediator pattern is by having the owning component handle events from its children, and in turn update the state of its children as necessary by passing changed data back down the tree. By adding a mediator, you can pass data across the tree using the familiar events-up, properties-down principle.

In the following example, the mediator element listens for events from the input and button elements in its shadow DOM. It controls the enabled state of the button so the user can only click Submit when there's text in the input.

{% playground-example "v3-docs/composition/mediator-pattern" "mediator-element.ts" %}

Other mediator patterns include flux/Redux-style patterns where a store mediates changes and updates components via subscriptions. Having components directly subscribe to changes can help avoid needing every parent to pass along all data required by its children.

Light DOM children

In addition to the nodes in your shadow DOM, you can render child nodes provided by the component user, like the standard <select> element can take a set of <option> elements as children and render them as menu items.

Child nodes are sometimes referred to as "light DOM" to distinguish them from the component's shadow DOM. For example:

<top-bar>
  <icon-button icon="menu" slot="nav-button"></icon-button>
  <span slot="title">Fuzzy</span>
</top-bar>

Here the top-bar element has two light DOM children supplied by the user: a navigation button, and a title.

Interacting with light DOM children is different from interacting with nodes in the shadow DOM. Nodes in a component's shadow DOM are managed by the component, and shouldn't be accessed from outside the component. Light DOM children are managed from outside the component, but can be accessed by the component as well. The component's user can add or remove light DOM children at any time, so the component can't assume a static set of child nodes.

The component has control over whether and where the child nodes are rendered, using the <slot> element in its shadow DOM. And it can receive notifications when child nodes are added and removed by listening for the slotchange event.

For more information, see the sections on rendering children with slots and accessing slotted children.


License

Japanese part

Creative Commons Attribution-NonCommercial 4.0 International Public License

Copyright (c) 2022 38elements

Other

Creative Commons Attribution 3.0 Unported

Copyright (c) 2020 Google LLC. All rights reserved.

BSD 3-Clause License

Copyright (c) 2020 Google LLC. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.