React Patterns

Contents

  • Stateless function
  • JSX spread attributes
  • Destructuring arguments
  • Conditional rendering
  • Children types
  • Array as children
  • Function as children
  • Render callback
  • Children pass-through
  • Proxy component
  • Style component
  • Event switch
  • Layout component
  • Container component
  • Higher-order component
  • State hoisting
  • Controlled input

Stateless function

Stateless functions are a brilliant way to define highly reusable components. They don’t hold state; they’re just functions.

const Greeting = () => <div>Hi there!</div>

They get passed props and context.

const Greeting = (props, context) =>
  <div style={{color: context.color}}>Hi {props.name}!</div>

They can define local variables, where a function block is used.

const Greeting = (props, context) => {
  const style = {
    fontWeight: "bold",
    color: context.color,
  }

  return <div style={style}>{props.name}</div>
}

But you could get the same result by using other functions.

const getStyle = context => ({
  fontWeight: "bold",
  color: context.color,
})

const Greeting = (props, context) =>
  <div style={getStyle(context)}>{props.name}</div>

They can have defined defaultPropspropTypes and contextTypes.

Greeting.propTypes = {
  name: PropTypes.string.isRequired
}
Greeting.defaultProps = {
  name: "Guest"
}
Greeting.contextTypes = {
  color: PropTypes.string
}

JSX spread attributes

Spread Attributes is a JSX feature. It’s syntactic sugar for passing all of an object’s properties as JSX attributes.

These two examples are equivalent.

// props written as attributes
<main className="main" role="main">{children}</main>

// props "spread" from object
<main {...{className: "main", role: "main", children}} />

Use this to forward props to underlying components.

const FancyDiv = props =>
  <div className="fancy" {...props} />

Now, I can expect FancyDiv to add the attributes it’s concerned with as well as those it’s not.

<FancyDiv data-id="my-fancy-div">So Fancy</FancyDiv>

// output: <div className="fancy" data-id="my-fancy-div">So Fancy</div>

Keep in mind that order matters. If props.className is defined, it’ll clobber the className defined by FancyDiv

<FancyDiv className="my-fancy-div" />

// output: <div className="my-fancy-div"></div>

We can make FancyDivs className always “win” by placing it after the spread props ({...props}).

// my `className` clobbers your `className`
const FancyDiv = props =>
  <div {...props} className="fancy" />

You should handle these types of props gracefully. In this case, I’ll merge the author’s props.className with the className needed to style my component.

const FancyDiv = ({ className, ...props }) =>
  <div
    className={["fancy", className].join(‘ ‘)}
    {...props}
  />

destructuring arguments

Destructuring assignment is an ES2015 feature. It pairs nicely with props in Stateless Functions.

These examples are equivalent.

const Greeting = props => <div>Hi {props.name}!</div>

const Greeting = ({ name }) => <div>Hi {name}!</div>

The rest parameter syntax (...) allows you to collect all the remaining properties in a new object.

const Greeting = ({ name, ...props }) =>
  <div>Hi {name}!</div>

In turn, this object can use JSX Spread Attributes to forward props to the composed component.

const Greeting = ({ name, ...props }) =>
  <div {...props}>Hi {name}!</div>

Avoid forwarding non-DOM props to composed components. Destructuring makes this very easy because you can create a new props object without component-specific props.

conditional rendering

You can’t use regular if/else conditions inside a component definition. The conditional (ternary) operator is your friend.

if

{condition && <span>Rendered when `truthy`</span> }

unless

{condition || <span>Rendered when `falsey`</span> }

if-else (tidy one-liners)

{condition
  ? <span>Rendered when `truthy`</span>
  : <span>Rendered when `falsey`</span>
}

if-else (big blocks)

{condition ? (
  <span>
    Rendered when `truthy`
  </span>
) : (
  <span>
    Rendered when `falsey`
  </span>
)}

Children types

React can render children of many types. In most cases it’s either an array or a string.

string

<div>
  Hello World!
</div>

array

<div>
  {["Hello ", <span>World</span>, "!"]}
</div>

Functions may be used as children. However, it requires coordination with the parent component to be useful.

function

<div>
  {(() => { return "hello world!"})()}
</div>

Array as children

Providing an array as children is a very common. It’s how lists are drawn in React.

We use map() to create an array of React Elements for every value in the array.

<ul>
  {["first", "second"].map((item) => (
    <li>{item}</li>
  ))}
</ul>

That’s equivalent to providing a literal array.

<ul>
  {[
    <li>first</li>,
    <li>second</li>,
  ]}
</ul>

This pattern can be combined with destructuring, JSX Spread Attributes, and other components, for some serious terseness.

<ul>
  {arrayOfMessageObjects.map(({ id, ...message }) =>
    <Message key={id} {...message} />
  )}
</ul>

Function as children

Using a function as children isn’t inherently useful.

<div>{() => { return "hello world!"}()}</div>

However, it can be used in component authoring for some serious power. This technique is commonly referred to as render callbacks.

This is a powerful technique used by libraries like ReactMotion. When applied, rendering logic can be kept in the owner component, instead of being delegated.

See Render callbacks, for more details.

Render callback

Here’s a component that uses a Render callback. It’s not useful, but it’s an easy illustration to start with.

const Width = ({ children }) => children(500)

The component calls children as a function, with some number of arguments. Here, it’s the number 500.

To use this component, we give it a function as children.

<Width>
  {width => <div>window is {width}</div>}
</Width>

We get this output.

<div>window is 500</div>

With this setup, we can use this width to make rendering decisions.

<Width>
  {width =>
    width > 600
      ? <div>min-width requirement met!</div>
      : null
  }
</Width>

If we plan to use this condition a lot, we can define another components to encapsulate the reused logic.

const MinWidth = ({ width: minWidth, children }) =>
  <Width>
    {width =>
      width > minWidth
        ? children
        : null
    }
  </Width>

Obviously a static Width component isn’t useful but one that watches the browser window is. Here’s a sample implementation.

class WindowWidth extends React.Component {
  constructor() {
    super()
    this.state = { width: 0 }
  }

  componentDidMount() {
    this.setState(
      {width: window.innerWidth},
      window.addEventListener(
        "resize",
        ({ target }) =>
          this.setState({width: target.innerWidth})
      )
    )
  }

  render() {
    return this.props.children(this.state.width)
  }
}

Many developers favor Higher Order Components for this type of functionality. It’s a matter of preference.

Children pass-through

You might create a component designed to apply context and render its children.

class SomeContextProvider extends React.Component {
  getChildContext() {
    return {some: "context"}
  }

  render() {
    // how best do we return `children`?
  }
}

You’re faced with a decision. Wrap children in an extraneous <div /> or return children directly. The first options gives you extra markup (which can break some stylesheets). The second will result in unhelpful errors.

// option 1: extra div
return <div>{children}</div>

// option 2: unhelpful errors
return children

It’s best to treat children as an opaque data type. React provides React.Children for dealing with childrenappropriately.

return React.Children.only(this.props.children)

Proxy component

(I’m not sure if this name makes sense)

Buttons are everywhere in web apps. And every one of them must have the type attribute set to “button”.

<button type="button">

Writing this attribute hundreds of times is error prone. We can write a higher level component to proxy props to a lower-level button component.

const Button = props =>
  <button type="button" {...props}>

We can use Button in place of button and ensure that the type attribute is consistently applied everywhere.

<Button />
// <button type="button"><button>

<Button className="CTA">Send Money</Button>
// <button type="button" class="CTA">Send Money</button>

Style component

This is a Proxy component applied to the practices of style.

Say we have a button. It uses classes to be styled as a “primary” button.

<button type="button" className="btn btn-primary">

We can generate this output using a couple single-purpose components.

import classnames from ‘classnames‘

const PrimaryBtn = props =>
  <Btn {...props} primary />

const Btn = ({ className, primary, ...props }) =>
  <button
    type="button"
    className={classnames(
      "btn",
      primary && "btn-primary",
      className
    )}
    {...props}
  />

It can help to visualize this.

PrimaryBtn()
  ? Btn({primary: true})
    ? Button({className: "btn btn-primary"}, type: "button"})
      ? ‘<button type="button" class="btn btn-primary"></button>‘

Using these components, all of these result in the same output.

<PrimaryBtn />
<Btn primary />
<button type="button" className="btn btn-primary" />

This can be a huge boon to style maintenance. It isolates all concerns of style to a single component.

Event switch

When writing event handlers it’s common to adopt the handle{eventName} naming convention.

handleClick(e) { /* do something */ }

For components that handle several event types, these function names can be repetitive. The names themselves might not provide much value, as they simply proxy to other actions/functions.

handleClick() { require("./actions/doStuff")(/* action stuff */) }
handleMouseEnter() { this.setState({ hovered: true }) }
handleMouseLeave() { this.setState({ hovered: false }) }

Consider writing a single event handler for your component and switching on event.type.

handleEvent({type}) {
  switch(type) {
    case "click":
      return require("./actions/doStuff")(/* action dates */)
    case "mouseenter":
      return this.setState({ hovered: true })
    case "mouseleave":
      return this.setState({ hovered: false })
    default:
      return console.warn(`No case for event type "${type}"`)
  }
}

Alternatively, for simple components, you can call imported actions/functions directly from components, using arrow functions.

<div onClick={() => someImportedAction({ action: "DO_STUFF" })}

Don’t fret about performance optimizations until you have problems. Seriously don’t.

Layout component

Layout components result in some form of static DOM element. It might not need to update frequently, if ever.

Consider a component that renders two children side-by-side.

<HorizontalSplit
  leftSide={<SomeSmartComponent />}
  rightSide={<AnotherSmartComponent />}
/>

We can aggressively optimize this component.

While HorizontalSplit will be parent to both components, it will never be their owner. We can tell it to update never, without interrupting the lifecycle of the components inside.

class HorizontalSplit extends React.Component {
  shouldComponentUpdate() {
    return false
  }

  render() {
    <FlexContainer>
      <div>{this.props.leftSide}</div>
      <div>{this.props.rightSide}</div>
    </FlexContainer>
  }
}

Container component

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

Given this reusable CommentList component.

const CommentList = ({ comments }) =>
  <ul>
    {comments.map(comment =>
      <li>{comment.body}-{comment.author}</li>
    )}
  </ul>

We can create a new component responsible for fetching data and rendering the stateless CommentList component.

class CommentListContainer extends React.Component {
  constructor() {
    super()
    this.state = { comments: [] }
  }

  componentDidMount() {
    $.ajax({
      url: "/my-comments.json",
      dataType: ‘json‘,
      success: comments =>
        this.setState({comments: comments});
    })
  }

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

We can write different containers for different application contexts.

Higher-order component

higher-order function is a function that takes and/or returns a function. It’s not more complicated than that. So, what’s a higher-order component?

If you’re already using container components, these are just generic containers, wrapped up in a function.

Let’s start with our stateless Greeting component.

const Greeting = ({ name }) => {
  if (!name) { return <div>Connecting...</div> }

  return <div>Hi {name}!</div>
}

If it gets props.name, it’s gonna render that data. Otherwise it’ll say that it’s “Connecting…”. Now for the the higher-order bit.

const Connect = ComposedComponent =>
  class extends React.Component {
    constructor() {
      super()
      this.state = { name: "" }
    }

    componentDidMount() {
      // this would fetch or connect to a store
      this.setState({ name: "Michael" })
    }

    render() {
      return (
        <ComposedComponent
          {...this.props}
          name={this.state.name}
        />
      )
    }
  }

This is just a function that returns component that renders the component we passed as an argument.

Last step, we need to wrap our our Greeting component in Connect.

const ConnectedMyComponent = Connect(Greeting)

This is a powerful pattern for providing fetching and providing data to any number of stateless function components.

State hoisting

Stateless functions don’t hold state (as the name implies).

Events are changes in state. Their data needs to be passed to stateful container components parents.

This is called “state hoisting”. It’s accomplished by passing a callback from a container component to a child component.

class NameContainer extends React.Component {
  render() {
    return <Name onChange={newName => alert(newName)} />
  }
}

const Name = ({ onChange }) =>
  <input onChange={e => onChange(e.target.value)} />

Name receives an onChange callback from NameContainer and calls on events.

The alert above makes for a terse demo but it’s not changing state. Let’s change the internal state of NameContainer.

class NameContainer extends React.Component {
  constructor() {
    super()
    this.state = {name: ""}
  }

  render() {
    return <Name onChange={newName => this.setState({name: newName})} />
  }
}

The state is hoisted to the container, by the provided callback, where it’s used to update local state. This sets a nice clear boundary and maximizes the re-usability of stateless function.

This pattern isn’t limited to stateless functions. Because stateless function don’t have lifecycle events, you’ll use this pattern with component classes as well.

Controlled input is an important pattern to know for use with state hoisting

(It’s best to process the event object on the stateful component)

Controlled input

It’s hard to talk about controlled inputs in the abstract. Let’s start with an uncontrolled (normal) input and go from there.

<input type="text" />

When you fiddle with this input in the browser, you see your changes. This is normal.

A controlled input disallows the DOM mutations that make this possible. You set the value of the input in component-land and it doesn’t change in DOM-land.

<input type="text" value="This won‘t change. Try it." />

Obviously static inputs aren’t very useful to your users. So, we derive a value from state.

class ControlledNameInput extends React.Component {
  constructor() {
    super()
    this.state = {name: ""}
  }

  render() {
    return <input type="text" value={this.state.name} />
  }
}

Then, changing the input is a matter of changing component state.

 return (
      <input
        value={this.state.name}
        onChange={e => this.setState({ name: e.target.value })}
      />
    )

This is a controlled input. It only updates the DOM when state has changed in our component. This is invaluable when creating consistent UIs.

If you’re using stateless functions for form elements, read about using state hoisting to move new state up the component tree

原文:https://reactpatterns.com/#render-callback

原文地址:https://www.cnblogs.com/mengff/p/9728804.html

时间: 2024-10-13 21:36:23

React Patterns的相关文章

react与jQuery对比,有空的时候再翻译一下

参考资料:http://reactfordesigners.com/labs/reactjs-introduction-for-people-who-know-just-enough-jquery-to-get-by/ Target Audience: People Who Know Just Enough jQuery to Get by Before I begin, I'd like to clarify who my target audience is. Zed Shaw, the a

Learning JavaScript Design Patterns -- A book by Addy Osmani

Learning JavaScript Design Patterns A book by Addy Osmani Volume 1.6.2 Tweet Copyright © Addy Osmani 2015. Learning JavaScript Design Patterns is released under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 unported license. It

Design Principles from Design Patterns

Leading-Edge Java A Conversation with Erich Gamma, Part III by Bill Venners June 6, 2005 Erich Gamma lept onto the software world stage in 1995 as co-author of the best-selling book Design Patterns: Elements of Reusable Object-Oriented Software (Addi

Vue.js Is Good, but Is It Better Than Angular or React?

Vue.js is a JavaScript library for building web interfaces. Combining ?with some other tools It also becomes a "framework". Now,?from our last blog?on?ValueCoders, you already know that Vue.js is one of the top JavaScript frameworks and it is re

13 Stream Processing Patterns for building Streaming and Realtime Applications

原文:https://iwringer.wordpress.com/2015/08/03/patterns-for-streaming-realtime-analytics/ Introduction More and more use cases, we want to react to data faster, rather than storing them in a disk and periodically processing and acting on the data. This

10分钟了解 react 引入的 Hooks

"大家好,我是谷阿莫,今天要将的是一个...",哈哈哈,看到这个题我就想到这个开头.最近react 官方在 2018 ReactConf 大会上宣布 React v16.7.0-alpha(内测) 将引入 Hooks.所以我们有必要了解 Hooks,以及由此引发的疑问. 当然,学习的最好.最直接的方法就是看文档,所以我也非常建议大家去看文档学习,而且还是官方的文档而不是中文版的文档.本文也是楼主在学习过后的一些总结与思考,楼主会把最近学习到的由浅入深,循序渐进,尽可能简洁的分享给大家,

[React] Use React.ReactNode for the children prop in React TypeScript components and Render Props

Because @types/react has to expose all its internal types, there can be a lot of confusion over how to type specific patterns, particularly around higher order components and render prop patterns. The widest and most recommended element type is React

React查缺补漏之二

译文链接 通过给一个通用函数传入参数定制特定函数的用法 _onFieldChange函数是一个通用实例方法,通过给这个函数传入不同的参数来实现返回结果的不同. 在构造函数中,进行绑定(没有想过这种用法). 1. `this._onNameChanged = this._onFieldChange.bind(this, 'name');` 2. `this._onPasswordChanged =this._onFieldChange.bind(this, 'password');` **注意点击

谈谈APP架构选型:React Native还是HBuilder

原文链接 导读:最近公司的一款新产品APP要进行研发,老大的意思想用H5来做混合APP以达到高效敏捷开发的目的.我自然就开始进行各种技术选型的调研,这里重点想说的是我最后挑选出的2款hybrid app开发技术方案:RN(react native),HBuilder.React Native是大名鼎鼎的Facebook的开源技术框架,而HBuilder是国内的H5工具开发公 司DCLOUD的产品.我自己先总结下吧:这两个技术框架在开发效率上基本上可以媲美WEB开发的速度,RN强调的是“Learn