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 defaultProps
, propTypes
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 FancyDiv
s 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 children
appropriately.
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
A 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
React Patterns的更多相关文章
- 4.2 react patterns(转)
修改 Props Immutable data representation 确定性 在 getInitialState 中使用 props 私有状态和全局事件 render 包含 side effe ...
- [Design Patterns] 4. Creation Pattern
设计模式是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结,使用设计模式的目的是提高代码的可重用性,让代码更容易被他人理解,并保证代码可靠性.它是代码编制真正实现工程化. 四个关键元素 ...
- react与jQuery对比,有空的时候再翻译一下
参考资料:http://reactfordesigners.com/labs/reactjs-introduction-for-people-who-know-just-enough-jquery-t ...
- [转] What is the point of redux when using react?
As I am sure you have heard a bunch of times, by now, React is the V in MVC. I think you can think o ...
- React组件设计
React组件设计 组件分类 展示组件和容器组件 展示组件 容器组件 关注事物的展示 关注事物如何工作 可能包含展示和容器组件,并且一般会有DOM标签和css样式 可能包含展示和容器组件,并且不会有D ...
- 1.5 A better alternative thing: React Native
In 2015, React Native (RN) was born. At that time, few people paid attention to it because it was st ...
- React的组件模式
组件是 React 的核心,因此了解如何利用它们对于创建优秀的设计结构至关重要. 什么是组件 根据 React 官网的介绍,"组件让你可以将 UI 分割成独立的.可重用的部分,并独立管理每个 ...
- Game Development Patterns and Best Practices (John P. Doran / Matt Casanova 著)
https://github.com/PacktPublishing/Game-Development-Patterns-and-Best-Practices https://github.com/m ...
- 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 ...
随机推荐
- django1.11+xadmin的搭建
1.git clone https://github.com/sshwsfc/xadmin.git或者直接下载zip包 2..在项目根目录下建一个extra_apps的包,将xadmin源码包存放在里 ...
- 用python读取配置文件config.ini
还在学习中...写的有点凌乱 感觉还是应该先学会读取配置文件才行,把一些经常需要修改的但是又经常需要用到的参数放到配置文件中方便使用(我是这么觉得的) 首先是config.ini的存放位置,我们把它放 ...
- [Clr via C#读书笔记]Cp5基元类型引用类型值类型
Cp5基元类型引用类型值类型 基元类型 编译器直接支持的类型,基元类型直接映射到FCL中存在的类型. 作者希望使用FCL类型名称而避免使用关键字.他的理由是为了更加的清晰的知道自己写的类型是哪种.但是 ...
- RNN概述-深度学习 -神经网络
一 RNN概述 前面我们叙述了BP算法, CNN算法, 那么为什么还会有RNN呢?? 什么是RNN, 它到底有什么不同之处? RNN的主要应用领域有哪些呢?这些都是要讨论的问题. 1) BP算法 ...
- 2018科大讯飞AI营销算法大赛全面来袭,等你来战!
AI技术已成为推动营销迭代的重要驱动力.AI营销高速发展的同时,积累了海量的广告数据和用户数据.如何有效应用这些数据,是大数据技术落地营销领域的关键,也是检测智能营销平台竞争力的标准. 讯飞AI营销云 ...
- leetcode个人题解——#19 Remove Nth Node From End of List
思路:设置两个指针,其中第二个指针比第一个延迟n个元素,这样,当第二个指针遍历到指针尾部时,对第一个指针进行删除操作. 当然,这题要注意一些边界值,比如输入[1,2] n=2时如果按照思路走会指向未分 ...
- Kali信息收集工具-dimtry
帮助文档 -s和-e参数需要用到google搜索 1.获取whois主机ip信息 2.扫描端口,根据banner信息判断服务
- 2018java开发一些面经
算法系列:https://www.cnblogs.com/yanmk/p/9232908.html 2018Java开发面经(持续更新) 不要给自己挖坑!!!不要给自己挖坑!!!不要给自己挖坑!!!如 ...
- POJ 3308 Paratroopers(最大流最小割の最小点权覆盖)
Description It is year 2500 A.D. and there is a terrible war between the forces of the Earth and the ...
- 迭代器类型:iterator & const_iterator
vector<int> ivec{1, 3, 4, 1, 3, 4}; vector<int>::iterator iter; // iter能读写vector<int& ...