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

  • React Components
  • Christopher Pitt
  • 1057字
  • 2021-07-09 19:34:45

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.

主站蜘蛛池模板: 乌兰县| 肥乡县| 耒阳市| 淅川县| 晋州市| 搜索| 图片| 祥云县| 凌云县| 和平区| 旬阳县| 临海市| 扎囊县| 安义县| 乌兰察布市| 天门市| 雅江县| 宁国市| 榆社县| 博爱县| 盐亭县| 连平县| 丹寨县| 甘洛县| 康定县| 大渡口区| 延川县| 呈贡县| 南木林县| 金门县| 上栗县| 长春市| 迁安市| 天峻县| 宁阳县| 乐至县| 凤台县| 海伦市| 理塘县| 乌什县| 灯塔市|