React模式和实践

不再维护。编写 React on Rails 的最合理模式。「Mostly reasonable patterns for writing React on Rails」

  • 所有者: chantastic/react-patterns
  • 平台: Web browsers
  • 许可证: MIT License
  • 分类:
  • 主题:
  • 喜欢:
    0
      比较:

Github星跟踪图

NO LONGER MAINTAINED

Moved and updated at reactpatterns.com.
Thanks for your help in developing this into the site.


React

Mostly reasonable patterns for writing React on Rails

Table of Contents

  1. Scope
  2. Organization
  3. Component Organization
  4. Formatting Props
  5. Patterns
  6. Computed Props
  7. Compound State
  8. prefer-ternary-to-sub-render
  9. View Components
  10. Container Components
  11. Anti-patterns
  12. Compound Conditions
  13. Cached State in render
  14. Existence Checking
  15. Setting State from Props
  16. Practices
  17. Naming Handle Methods
  18. Naming Events
  19. Using PropTypes
  20. Using Entities
  21. Gotchas
  22. Tables
  23. Libraries
  24. classnames
  25. Other
  26. JSX
  27. ES2015
  28. react-rails
  29. rails-assets
  30. flux

Scope

This is how we write React.js on Rails.
We've struggled to find the happy path. Recommendations here represent a good
number of failed attempts. If something seems out of place, it probably is;
let us know what you've found.

All examples written in ES2015 syntax now that the
official react-rails gem ships with
babel.

⬆ back to top


Component Organization

  • class definition
    • constructor
      • event handlers
    • 'component' lifecycle events
    • getters
    • render
  • defaultProps
  • proptypes
class Person extends React.Component {
  constructor (props) {
    super(props);

    this.state = { smiling: false };

    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling});
    };
  }

  componentWillMount () {
    // add event listeners (Flux Store, WebSocket, document, etc.)
  }

  componentDidMount () {
    // React.getDOMNode()
  }

  componentWillUnmount () {
    // remove event listeners (Flux Store, WebSocket, document, etc.)
  }

  get smilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }

  render () {
    return (
      <div onClick={this.handleClick}>
        {this.props.name} {this.smilingMessage}
      </div>
    );
  }
}

Person.defaultProps = {
  name: 'Guest'
};

Person.propTypes = {
  name: React.PropTypes.string
};

⬆ back to top

Formatting Props

Wrap props on newlines for exactly 2 or more.

// bad
<Person
 firstName="Michael" />

// good
<Person firstName="Michael" />
// bad
<Person firstName="Michael" lastName="Chan" occupation="Designer" favoriteFood="Drunken Noodles" />

// good
<Person
 firstName="Michael"
 lastName="Chan"
 occupation="Designer"
 favoriteFood="Drunken Noodles" />

⬆ back to top


Computed Props

Use getters to name computed properties.

  // bad
  firstAndLastName () {
    return `${this.props.firstName} ${this.props.lastName}`;
  }

  // good
  get fullName () {
    return `${this.props.firstName} ${this.props.lastName}`;
  }

See: Cached State in render anti-pattern

⬆ back to top


Compound State

Prefix compound state getters with a verb for readability.

// bad
happyAndKnowsIt () {
  return this.state.happy && this.state.knowsIt;
}
// good
get isHappyAndKnowsIt () {
  return this.state.happy && this.state.knowsIt;
}

These methods MUST return a boolean value.

See: Compound Conditions anti-pattern

⬆ back to top

Prefer Ternary to Sub-render

Keep logic inside the render function.

// bad
renderSmilingStatement () {
  return <strong>{(this.state.isSmiling) ? " is smiling." : ""}</strong>;
},

render () {
  return <div>{this.props.name}{this.renderSmilingStatement()}</div>;
}
// good
render () {
  return (
    <div>
      {this.props.name}
      {(this.state.smiling)
        ? <span>is smiling</span>
        : null
      }
    </div>
  );
}

⬆ back to top

View Components

Compose components into views. Don't create one-off components that merge layout
and domain components.

// bad
class PeopleWrappedInBSRow extends React.Component {
  render () {
    return (
      <div className="row">
        <People people={this.state.people} />
      </div>
    );
  }
}
// good
class BSRow extends React.Component {
  render () {
    return <div className="row">{this.props.children}</div>;
  }
}

class SomeView extends React.Component {
  render () {
    return (
      <BSRow>
        <People people={this.state.people} />
      </BSRow>
    );
  }
}

⬆ back to top

Container Components

A container does data fetching and then renders its corresponding
sub-component. That's it. — Jason Bonta

Bad

// CommentList.js

class CommentList extends React.Component {
  getInitialState () {
    return { comments: [] };
  }

  componentDidMount () {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }

  render () {
    return (
      <ul>
        {this.state.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}

Good

// CommentList.js

class CommentList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}
// CommentListContainer.js

class CommentListContainer extends React.Component {
  getInitialState () {
    return { comments: [] }
  }

  componentDidMount () {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }

  render () {
    return <CommentList comments={this.state.comments} />;
  }
}

Read more
Watch more

⬆ back to top


Cached State in render

Do not keep state in render

// bad
render () {
  let name = `Mrs. ${this.props.name}`;

  return <div>{name}</div>;
}

// good
render () {
  return <div>{`Mrs. ${this.props.name}`}</div>;
}
// best
get fancyName () {
  return `Mrs. ${this.props.name}`;
}

render () {
  return <div>{this.fancyName}</div>;
}

This is mostly stylistic and keeps diffs nice. I doubt that there's a significant perf reason to do this.

See: Computed Props pattern

⬆ back to top

Compound Conditions

Don't put compound conditions in render.

// bad
render () {
  return <div>{if (this.state.happy && this.state.knowsIt) { return "Clapping hands" }</div>;
}
// better
get isTotesHappy() {
  return this.state.happy && this.state.knowsIt;
},

render() {
  return <div>{(this.isTotesHappy) && "Clapping hands"}</div>;
}

The best solution for this would use a container
component
to manage state and
pass new state down as props.

See: Compound State pattern

⬆ back to top

Existence Checking

Do not check existence of props at the root of a component.
Components should not have two possible return types.

// bad
const Person = props => {
  if (this.props.firstName)
    return <div>{this.props.firstName}</div>
  else
    return null
}

Components should always render. Consider adding defaultProps, where a sensible default is appropriate.

// better
const Person = props =>
  <div>{this.props.firstName}</div>

Person.defaultProps = {
  firstName: "Guest"
}

If a component should be conditionally rendered, handle that in the owner component.

// best
const TheOwnerComponent = props =>
  <div>
    {props.person && <Person {...props.person} />}
  </div>

This is only where objects or arrays are used. Use PropTypes.shape to clarify
the types of nested data expected by the component.

⬆ back to top

Setting State from Props

Do not set state from props without obvious intent.

// bad
getInitialState () {
  return {
    items: this.props.items
  };
}
// good
getInitialState () {
  return {
    items: this.props.initialItems
  };
}

Read: "Props in getInitialState Is an Anti-Pattern"

⬆ back to top


Naming Handler Methods

Name the handler methods after their triggering event.

// bad
punchABadger () { /*...*/ },

render () {
  return <div onClick={this.punchABadger} />;
}
// good
handleClick () { /*...*/ },

render () {
  return <div onClick={this.handleClick} />;
}

Handler names should:

  • begin with handle
  • end with the name of the event they handle (eg, Click, Change)
  • be present-tense

If you need to disambiguate handlers, add additional information between
handle and the event name. For example, you can distinguish between onChange
handlers: handleNameChange and handleAgeChange. When you do this, ask
yourself if you should be creating a new component.

⬆ back to top

Naming Events

Use custom event names for ownee events.

class Owner extends React.Component {
  handleDelete () {
    // handle Ownee's onDelete event
  }

  render () {
    return <Ownee onDelete={this.handleDelete} />;
  }
}

class Ownee extends React.Component {
  render () {
    return <div onChange={this.props.onDelete} />;
  }
}

Ownee.propTypes = {
  onDelete: React.PropTypes.func.isRequired
};

⬆ back to top

Using PropTypes

Use PropTypes to communicate expectations and log meaningful warnings.

MyValidatedComponent.propTypes = {
  name: React.PropTypes.string
};

MyValidatedComponent will log a warning if it receives name of a type other than string.

<Person name=1337 />
// Warning: Invalid prop `name` of type `number` supplied to `MyValidatedComponent`, expected `string`.

Components may also require props.

MyValidatedComponent.propTypes = {
  name: React.PropTypes.string.isRequired
}

This component will now validate the presence of name.

<Person />
// Warning: Required prop `name` was not specified in `Person`

Read: Prop Validation

⬆ back to top

Using Entities

Use React's String.fromCharCode() for special characters.

// bad
<div>PiCO · Mascot</div>

// nope
<div>PiCO &middot; Mascot</div>

// good
<div>{'PiCO ' + String.fromCharCode(183) + ' Mascot'}</div>

// better
<div>{`PiCO ${String.fromCharCode(183)} Mascot`}</div>

Read: JSX Gotchas

⬆ back to top

Tables

The browser thinks you're dumb. But React doesn't. Always use tbody in
table components.

// bad
render () {
  return (
    <table>
      <tr>...</tr>
    </table>
  );
}

// good
render () {
  return (
    <table>
      <tbody>
        <tr>...</tr>
      </tbody>
    </table>
  );
}

The browser is going to insert tbody if you forget. React will continue to
insert new trs into the table and confuse the heck out of you. Always use
tbody.

⬆ back to top

classnames

Use classNames to manage conditional classes.

// bad
get classes () {
  let classes = ['MyComponent'];

  if (this.state.active) {
    classes.push('MyComponent--active');
  }

  return classes.join(' ');
}

render () {
  return <div className={this.classes} />;
}
// good
render () {
  let classes = {
    'MyComponent': true,
    'MyComponent--active': this.state.active
  };

  return <div className={classnames(classes)} />;
}

Read: Class Name Manipulation

⬆ back to top

JSX

We used to have some hardcore CoffeeScript lovers is the group. The unfortunate
thing about writing templates in CoffeeScript is that it leaves you on the hook
when certain implementations changes that JSX would normally abstract.

We no longer recommend using CoffeeScript to write render.

For posterity, you can read about how we used CoffeeScript, when using CoffeeScript was
non-negotiable: CoffeeScript and JSX.

⬆ back to top

ES2015

react-rails now ships with babel. Anything
you can do in Babel, you can do in Rails. See the documentation for additional config.

⬆ back to top

react-rails

react-rails should be used in all
Rails apps that use React. It provides the perfect amount of glue between Rails
conventions and React.

⬆ back to top

rails-assets

rails-assets should be considered for bundling
js/css assets into your applications. The most popular React-libraries we use
are registered on Bower and can be easily added through
Bundler and react-assets.

caveats: rails-assets gives you access to bower projects via Sprockets
requires. This is a win for the traditionally hand-wavy approach that Rails
takes with JavaScript. This approach doesn't buy you modularity or the ability to
interop with JS tooling that requires modules.

⬆ back to top

flux

Use Alt for flux implementation. Alt is true to the flux
pattern with the best documentation available.

⬆ back to top

主要指标

概览
名称与所有者chantastic/react-patterns
主编程语言
编程语言 (语言数: 0)
平台Web browsers
许可证MIT License
所有者活动
创建于2014-09-04 17:00:29
推送于2019-07-10 00:05:33
最后一次提交2019-07-09 17:05:33
发布数0
用户参与
星数1.7k
关注者数72
派生数111
提交数72
已启用问题?
问题数13
打开的问题数3
拉请求数22
打开的拉请求数0
关闭的拉请求数7
项目设置
已启用Wiki?
已存档?
是复刻?
已锁定?
是镜像?
是私有?