React and React Native
上QQ阅读APP看书,第一时间看更新

Creating your own JSX elements

Components are the fundamental building blocks of React. In fact, components are the vocabulary of JSX markup. In this section, we'll see how to encapsulate HTML markup within a component. We'll build examples that show you how to nest custom JSX elements and how to namespace your components.

Encapsulating HTML

The reason that we want to create new JSX elements is so that we can encapsulate larger structures. This means that instead of having to type out complex markup, we just use our custom tag. The React component returns the JSX that replaces the element. Let's look at an example now:

// We also need "Component" so that we can 
// extend it and make a new JSX tag. 
import React, { Component } from 'react'; 
import { render } from 'react-dom'; 
 
// "MyComponent" extends "Component", which means that 
// we can now use it in JSX markup. 
class MyComponent extends Component { 
  render() { 
    // All components have a "render()" method, which 
    // returns some JSX markup. In this case, "MyComponent" 
    // encapsulates a larger HTML structure. 
    return ( 
      <section> 
        <h1>My Component</h1> 
        <p>Content in my component...</p> 
      </section> 
    ); 
  } 
} 
 
// Now when we render "<MyComponent>" tags, the encapsulated 
// HTML structure is actually rendered. These are the 
// building blocks of our UI. 
render( 
  <MyComponent />, 
  document.getElementById('app') 
); 

Here's what the rendered output looks like:

This is the first React component that we've implemented in this book, so let's take a moment to dissect what's going on here. We've created a class called MyComponent that extends the Component class from React. This is how we create a new JSX element. As you can see in the call to render(), we're rendering a <MyComponent> element.

The HTML that this component encapsulates is returned by the render() method. In this case, when the JSX <MyComponent> is rendered by react-dom, it's replaced by a <section> element, and everything within it.

Note

When we render JSX, any custom elements we use must have their corresponding React component within the same scope. In the preceding example, the class MyComponent was declared in the same scope as the call to render(), so everything worked as expected. Usually, we'll import components, adding them to the appropriate scope. We'll see more of this as we progress through the book.

Nested elements

Using JSX markup is useful for describing UI structures that have parent-child relationships. For example, a <li> tag is only useful as the child of a <ul> or <ol> tag. Therefore, we're probably going to make similar nested structures with our own React components. For this, you need to use the children property. Let's see how this works; here's the JSX markup that we want to render:

import React from 'react'; 
import { render } from 'react-dom'; 
 
// Imports our two components that render children... 
import MySection from './MySection'; 
import MyButton from './MyButton'; 
 
// Renders the "MySection" element, which has a child 
// component of "MyButton", which in turn has child text. 
render(( 
  <MySection> 
    <MyButton>My Button Text</MyButton> 
  </MySection> 
  ), 
  document.getElementById('app') 
); 

Here, you can see that we're importing two of our own React components: MySection and MyButton. Now, if you look at the JSX that we're rendering, you'll notice that <MyButton> is a child of <MySection>. You'll also notice that the MyButton component accepts text as its child, instead of more JSX elements. Let's see how these components work, starting with MySection:

import React, { Component } from 'react'; 
 
// Renders a "<section>" element. The section has 
// a heading element and this is followed by 
// "this.props.children". 
export default class MySection extends Component { 
  render() { 
    return ( 
      <section> 
        <h2>My Section</h2> 
        {this.props.children} 
      </section> 
    ); 
  } 
} 

This component renders a standard <section> HTML element, a heading, and then {this.props.children}. It's this last construct that allows components to access nested elements or text, and to render it.

Note

The two braces used in the preceding example are used for JavaScript expressions. We'll touch on more details of the JavaScript expression syntax found in JSX markup in the following section.

Now, let's look at the MyButton component:

import React, { Component } from 'react'; 
 
// Renders a "<button>" element, using 
// "this.props.children" as the text. 
export default class MyButton extends Component { 
  render() { 
    return ( 
      <button>{this.props.children}</button> 
    ); 
  } 
} 

This component is using the exact same pattern as MySection; take the {this.props.children} value, and surround it with meaningful markup. React handles a lot of messy details for us. In this example, the button text is a child of MyButton, which is in turn a child of MySection. However, the button text is transparently passed through MySection. In other words, we didn't have to write any code in MySection to make sure that MyButton got it's text. Pretty cool, right? Here's what the rendered output looks like:

Namespaced components

The custom elements we've created so far have used simple names. Sometimes, we might want to give a component a namespace. Instead of writing <MyComponent> in our JSX markup, we would write <MyNamespace.MyComponent>. This makes it clear to anyone reading the JSX that MyComponent is part of MyNamespace.

Typically, MyNamespace would also be a component. The idea with namespacing is to have a namespace component render its child components using the namespace syntax. Let's take a look at an example:

import React from 'react'; 
import { render } from 'react-dom'; 
 
// We only need to import "MyComponent" since 
// the "First" and "Second" components are part 
// of this "namespace". 
import MyComponent from './MyComponent'; 
 
// Now we can render "MyComponent" elements, 
// and it's "namespaced" elements as children. 
// We don't actually have to use the namespaced 
// syntax here, we could import the "First" and 
// "Second" components and render them without the 
// "namespace" syntax. It's a matter of readability 
// and personal taste. 
render(( 
  <MyComponent> 
    <MyComponent.First /> 
    <MyComponent.Second /> 
  </MyComponent> 
  ), 
  document.getElementById('app') 
); 

This markup renders a <MyComponent> element with two children. The key thing to notice here is that instead of writing <First>, we write <MyComponent.First>. Same with <MyComponent.Second>. The idea is that we want to explicitly show that First and Second belong to MyComponent, within the markup.

Note

I personally don't depend on namespaced components like these, because I'd rather see which components are in use by looking at the import statements at the top of the module. Others would rather import one component and explicitly mark the relationship within the markup. There is no correct way to do this; it's a matter of personal taste.

Now let's take a look at the MyComponent module:

import React, { Component } from 'react'; 
 
// The "First" component, renders some basic JSX... 
class First extends Component { 
  render() { 
    return ( 
      <p>First...</p> 
    ); 
  } 
} 
 
// The "Second" component, renders some basic JSX... 
class Second extends Component { 
  render() { 
    return ( 
      <p>Second...</p> 
    ); 
  } 
} 
 
// The "MyComponent" component renders it's children 
// in a "<section>" element. 
class MyComponent extends Component { 
  render() { 
    return ( 
      <section> 
        {this.props.children} 
      </section> 
    ); 
  } 
} 
 
// Here is where we "namespace" the "First" and 
// "Second" components, by assigning them to 
// "MyComponent" as class properties. This is how 
// other modules can render them as "<MyComponent.First>" 
// elements. 
MyComponent.First = First; 
MyComponent.Second = Second; 
 
export default MyComponent; 
 
// This isn't actually necessary. If we want to be able 
// to use the "First" and "Second" components independent 
// of "MyComponent", we would leave this in. Otherwise, 
// we would only export "MyComponent". 
   export { First, Second }; 

You can see that this module declares MyComponent as well as the other components that fall under this namespace (First and Second). The idea is to assign the components to the namespace component (MyComponent) as class properties. There are a number of things we could change in this module. For example, we don't have to directly export First and Second since they're accessible through MyComponent. We also don't need to define everything in the same module; we could import First and Second and assign them as class properties. Using namespaces is completely optional, and if you use them, you should use them consistently.