官术网_书友最值得收藏!

Shared component actions

So, how do we change from a PageView class to a PageEditor class? For that, we need to hook into browser events and fiddle with the state:

class Page extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
 "isEditing": false
 };
    }

    render() {
        if (this.state.isEditing) {
            return <PageEditor
                {...this.props}
                onCancel={this.onCancel.bind(this)}
                />;
        }

        return <PageView
            {...this.props}
            onPageEdit={this.onEdit.bind(this)}
            />;
    }

    onEdit() {
 this.setState({
 "isEditing": true
 });
 }

 onCancel() {
 this.setState({
 "isEditing": false
 });
 }
}

We're providing a way for child components to call methods in parent components by passing down methods child components can use. When a PageView class wants to put the Page into edit mode, it can call this.props.onEdit. The Page will know how to handle that. We'll see this pattern often, so it's good to understand what it's doing here before moving on!

In the same way, we provide a way for a PageEditor class to cancel edit mode. In both these cases, we use setState to switch between editing and viewing states.

Note

We bind the handle methods, because otherwise this will mean something different when the methods are called. Binding like this is not efficient, so we'll revisit this later with an alternative!

We can connect these handlers to click events in each component:

class PageEditor extends React.Component {
    render() {
        return <form>
            <input type="text" name="title" />
            <textarea name="body"></textarea>
            <button>save</button>
            <button
                onClick={this.onCancel.bind(this)}
                >back</button>
        </form>;
    }

    onCancel(event) {
        event.preventDefault();
        this.props.onCancel();
    }
}

We need to prevent default form submission before calling the onCancel passed down through props. The code is as follows:

class PageView extends React.Component {
    render() {
        return <p>
            {this.props.title}
            <button
                onClick={this.props.onEdit}
                >edit</button>
            <button>delete</button>
        </p>;
    }
}

You should now be able to run this in a browser and toggle between the edit and view aspects of each page. This is a good time to stop and take stock of what we've achieved:

  1. We created an entry-point component to page management called PageAdmin. This component handles fetching and persisting page data. It uses a Backend class to do these. It also renders Page components for each page that Backend returns.
  2. We created a Page component to encapsulate page data as well as edit and view aspects of each page. The Page component handles switching between these two child components, via callbacks.
  3. We created PageEditor as an interface for editing page data. It contains a couple of fields, which we'll shortly discuss.
  4. Finally, we created PageView as an interface for viewing page data and getting to the edit mode. We're about to make the Delete button work too.

If you've been following along, your interface may look something like this:

We have created new function references throughout this chapter. Every time we use fn.bind(this), we create a new function. This is inefficient if we're doing it inside render methods. We can get around this by creating a base component:

import React from "react";

class Component extends React.Component {
    bind(...methods) {
        methods.map(
            method => this[method] = this[method].bind(this)
        )
    }
}

export default Component;

If we extend this base component (instead of the usual React.Component), then we will have access to the bind method. It takes one or more function names, and replaces them with bound versions.

Now, we need to add event handlers for updating and deleting pages. Let's start with PageView and PageEditor:

import Component from "component";

class PageView extends Component {
    constructor(props) {
        super(props);

        this.bind(
 "onDelete"
 );
    }

    render() {
        return <p>
            {this.props.title}
            <button
                onClick={this.props.onEdit}
                >edit</button>
            <button
                onClick={this.onDelete}
                >delete</button>
        </p>;
    }

    onDelete() {
 this.props.onDelete(
 this.props.id
 );
 }
}

We added an onClick handler to the Delete button. This will trigger a bound version of onDelete in which we pass the correct:

import Component from "component";

class PageEditor extends Component {
    constructor(props) {
        super(props);

        this.bind(
 "onCancel",
 "onUpdate"
 );
    }

    render() {
        return <form>
            <input
                type="text"
                name="title"
                value={this.props.title}
 onChange={this.onUpdate}
               />
           <textarea
               name="body"
               value={this.props.body}
 onChange={this.onUpdate}>
 </textarea>
            <button
                onClick={this.onCancel}>
                cancel
            </button>
        </p>;
    }

 onUpdate() {
 this.props.onUpdate(
 this.props.id,
 event.target.name,
 event.target.value
 );
 }

 onCancel(event) {
 event.preventDefault();
 this.props.onCancel();
 }
}

Here, we added onUpdate so that we can determine which input changed. It calls the props onUpdate method with the correct property name and value.

We also add the name and value attributes for the inputs, setting the values to the corresponding properties. These updates are triggered when the inputs change, calling the onUpdate method. This means property updates will reflect in the fields.

Where do these new handler properties come from? We need to add them to PageAdmin:

import Component from "component";

class PageAdmin extends Component {
    constructor(props) {
        super(props);

        this.state = {
            "pages": []
        };

 this.bind(
 "onUpdate",
 "onDelete"
 );
    }

    componentWillMount() {
        this.setState({
            "pages": this.props.backend.getAll()
        });
    }

    render() {
        return <ol>
            {this.state.pages.map(function(page) {
                return <li key={page.id}>
                    <Page
                        {...page}
                        onUpdate={this.onUpdate}
                        onDelete={this.onDelete}
                        />
                </li>;
            })}
        </ol>;
    }

    onUpdate(...params) {
 this.props.backend.update(...params);
 }

 onDelete(...params) {
 this.props.backend.delete(...params);
 }
}

Finally, we create a couple of methods to handle updates and deletes. These are bound, as we've been doing to methods in the other classes. They also use the rest/spread operators as a bit of a shortcut!

We can fake the backend data and operations with an array of pages and a few array modifier methods:

class Backend {
    constructor() {
        this.deleted = [];
 this.updates = [];

 this.pages = [
 {
 "id": 1,
 "title": "Home",
 "body": "..."
 },
 {
 "id": 2,
 "title": "About Us",
 "body": "..."
 },
 {
 "id": 3,
 "title": "Contact Us",
 "body": "..."
 },
 {
 "id": 4,
 "title": "Products",
 "body": "..."
 }
 ];
    }

    getAll() {
        return this.pages
 .filter(page => {
 return this.deleted.indexOf(page.id) == -1
 })
 .map(page => {
 var modified = page;

 this.updates.forEach((update) => {
 if (update[0] == page.id) {
 modified[update[1]] = update[2];
 }
 });
 return modified;
 });
    }

    update(id, property, value) {
        this.updates.push([id, property, value]);
    }

    delete(id) {
        this.deleted.push(id);
    }
}
Tip

This is by no means an efficient implementation. Please do not use this code in production. It's just an example interface against which we can test our code!

The all method returns a filtered and mapped array of initial pages. The () => {} syntax is a shortcut for (function(){}).bind(this). The brackets are even optional, if there is exactly one property for the function. The filter checks that each page id is not in the deleted array. We're not actually deleting pages in this pretend backend. We're simply excluding ones we know we don't want to see.

We don't update the pages directly, but we apply updates to the array before all returns it. This isn't efficient, but it does allow us to see out interface in action.

Note

You can learn more about these array tricks at https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array. It's a great place to learn about JavaScript language features.

主站蜘蛛池模板: 定安县| 嘉峪关市| 白玉县| 蒙山县| 台前县| 三原县| 股票| 司法| 瓮安县| 西华县| 林西县| 酉阳| 上饶市| 岱山县| 衡山县| 阿鲁科尔沁旗| 怀来县| 鄂托克旗| 贡嘎县| 兴城市| 浦城县| 凭祥市| 天津市| 河西区| 西乌珠穆沁旗| 道孚县| 绥芬河市| 连江县| 红桥区| 隆化县| 芦山县| 界首市| 鄢陵县| 渝北区| 镶黄旗| 崇礼县| 饶平县| 通化县| 大港区| 开平市| 泽州县|