使用 Controller View 编写 ReactJs 应用

Not to be confused with MVC, a “view controller” or “controller view” is a top level component that holds all state and passes it to children as props.

For any top level component, such as a <HomePage />, create a <HomePageController /> that passes all needed data as props to <HomePage />in the render function.

// Controller views are very simple
class HomePageController extends React.Component {

    // Normal Flux store listening
    componentDidMount() {
        Store1.on('change', this.onStoreChange);
        Store2.on('change', this.onStoreChange);
    }

    onStoreChange() {
        this.setState({
            data1: Store1.getData(),
            data2: Store2.getData()
        });
    }

    render() {
        // <HomePage /> has no internal state!
        return <HomePage
            data1={this.state.data1}
            data2={this.state.data2} />;
    }

}

Benefits of Controller Views

Components are More Portable

Let’s say you have a <ShoppingCartView /> component, and a ShoppingCartStore. At first glance, it makes sense to have the component listen directly to the store.

But what if you later need two <ShoppingCartView /> components reading from different stores on one page? If you already have a <ShoppingCartController />, you can listen to multiple stores in the controller and render each child with props:

class ShoppingCartController extends React.Component {  
    ...

    // You get component portability for free!
    render() {
        return <div>
            <ShoppingCart data={store1Data} />
            <ShoppingCart data={store2Data} />
        </div>;
    }

Works Wonderfully With React Router

React Router has <RouteHandler /> components which are entry points for each page. If you have a view controller you already have a route handler!

<Route name="home" path="/" handler={ HomePageController } />

This is especially helpful if you need to read URL data in your components, such as context.router.getCurrentQuery(). You pass URL data as props instead of messing with it in your render function. Now your child component renders purely on props, instead of managing the URL data itself!

You can also put willTransitionTo hooks on the controller view, making your child components even more portable. They never have to worry about routing directly.

Less Cognitive Overhead

State is generally disliked in computer programming because it becomes harder to understand a function at a glance. If you have nested components that all manage their own state, it’s harder to reason about your application. When a component renders purely based on props, it’s easier to reason about the component output, because there are definitive inputs.

Easier Testing

To test child components, simply render them with specific props and verify the rendered output. You don’t have to mock any stores or action creators.

Questions Answered

Nested View Controllers?

Controller views aren’t always top level components! Consider a <HomePageController /> component that can render a <SignupLightbox />. Technically, the lightbox is nested inside the controller view. Should the <HomePageController /> keep all state needed to render the lightbox?

No. If the lightbox can appear on multiple pages, it’s a fine pattern to make a <SignupLightboxController /> rendered inside your <HomePageController />. The lightbox is conceptually a “top level” component, even though it’s technically nested inside a view controller.

Pass Action Creators as Props?

Sometimes your controller view can pass more than data as props. Consider a multi-step form inside a controller view. The “next” action of the form changes based on current step you’re on. The controller view can pass a nextAction prop to the child. The child form can then call Dispatcher.dispatch( this.props.nextAction ), and doesn’t need to know what the next step is.

Can Child Components Have Any Internal State?

Yes. In fact, with the controller view pattern, it’s much more obviouswhen to use internal state. For example, if you have a <SearchForm />, it could have this.state.unsubmittedSearch, which is just temporary data that only the child component needs to know about.

If your data never needs to flow through your stores, child state is likely a good place for it.

Concerns

The main concern of this pattern is deep component nesting. Controller views should be small and simple. If you have deeply nested child components your controller views can become large and unwieldy, as you must pass dozens of props and manage considerable state.

The solution to this is application specific, but this is generally a code smell. Evaluate your component tree to see if a nested controller view makes sense, or if you can break up your top level controller view into smaller ones.

For example, if you have a 20 page multi-step form, you’ll need to pass a lot of props. But if every step has a controller view, and is accessed from a specific URL, your components become smaller and simpler.

 

转载: https://blog.andrewray.me/the-reactjs-controller-view-pattern/