中文版:https://reactpatterns.cn/
原版:https://reactpatterns.comhtml
函數組件 是最簡單的一種聲明可複用組件的方法react
他們就是一些簡單的函數。web
function Greeting() { return <div>Hi there!</div>; }
從第一個形參中獲取屬性集 (props)ajax
function Greeting(props) { return <div>Hi {props.name}!</div>; }
按本身的須要能夠在函數組件中定義任意變量json
最後必定要返回你的 React 組件。數組
function Greeting(props) { let style = { fontWeight: "bold", color: context.color }; return <div style={style}>Hi {props.name}!</div>; }
使用 defaultProps
爲任意必有屬性設置默認值瀏覽器
function Greeting(props) { return <div>Hi {props.name}!</div>; } Greeting.defaultProps = { name: "Guest" };
解構賦值 是一種 JavaScript 特性。 性能優化
出自 ES2015 版的 JavaScript 新規範。ide
因此看起來可能並不常見。函數
比如字面量賦值的反轉形式。
let person = { name: "chantastic" }; let { name } = person;
一樣適用於數組。
let things = ["one", "two"]; let [first, second] = things;
解構賦值被用在不少 函數組件 中。
下面聲明的這些組件是相同的。
function Greeting(props) { return <div>Hi {props.name}!</div>; } function Greeting({ name }) { return <div>Hi {name}!</div>; }
有一種語法能夠在對象中收集剩餘屬性。
叫作 剩餘參數,看起來就像這樣。
function Greeting({ name, ...restProps }) { return <div>Hi {name}!</div>; }
那三個點 (...
) 會把全部的剩餘屬性分配給 restProps
對象
然而,你能使用 restProps
作些什麼呢?
繼續往下看...
屬性展開是 JSX 中的一個的特性。
它是一種語法,專門用來把對象上的屬性轉換成 JSX 中的屬性
參考上面的 屬性解構),
咱們能夠 擴散 restProps
對象的全部屬性到 div 元素上
function Greeting({ name, ...restProps }) { return <div {...restProps}>Hi {name}!</div>; }
這讓 Gretting
組件變得很是靈活。
咱們能夠經過傳給 Gretting 組件 DOM 屬性並肯定這些屬性必定會被傳到 div
上
<Greeting name="Fancy pants" className="fancy-greeting" id="user-greeting" />
避免傳遞非 DOM 屬性到組件上。
解構賦值是如此的受歡迎,是由於它能夠分離 組件特定的屬性
和 DOM/平臺特定屬性
function Greeting({ name, ...platformProps }) { return <div {...platformProps}>Hi {name}!</div>; }
組件就是一種抽象。
好的抽象是能夠擴展的。
好比說下面這個組件使用 class
屬性來給按鈕添加樣式。
function MyButton(props) { return <button className="btn" {...props} />; }
通常狀況下這樣作就夠了,除非咱們須要擴展其它的樣式類
<MyButton className="delete-btn">Delete...</MyButton>
在這個例子中把 btn
替換成 delete-btn
JSX 中的屬性展開) 對前後順序是敏感的
擴散屬性中的 className
會覆蓋組件上的 className
。
咱們能夠改變它兩的順序,可是目前來講 className
只有 btn
。
function MyButton(props) { return <button {...props} className="btn" />; }
咱們須要使用解構賦值來合併入參 props 中的 className
和基礎的(組件中的) className
。
能夠經過把全部的值放在一個數組裏面,而後使用一個空格鏈接它們。
function MyButton({ className, ...props }) { let classNames = ["btn", className].join(" "); return <button className={classNames} {...props} />; }
爲了保證 undefined
不被顯示在 className 上,可使用 默認值。
function MyButton({ className = "", ...props }) { let classNames = ["btn", className].join(" "); return <button className={classNames} {...props} />; }
不能夠在一個組件聲明中使用 if/else 語句
You can't use if/else statements inside a component declarations.
因此可使用 條件(三元)運算符 和 短路計算。
若是
{ condition && <span>Rendered when `truthy`</span>; }
除非
{ condition || <span>Rendered when `falsy`</span>; }
若是-不然
{ condition ? ( <span>Rendered when `truthy`</span> ) : ( <span>Rendered when `falsy`</span> ); }
不少類型均可以作爲 React 的子元素。
多數狀況下會是 數組
或者 字符串
。
String
<div>Hello World!</div>
Array
<div>{["Hello ", <span>World</span>, "!"]}</div>
將數組作爲子元素是很常見的。
列表是如何在 React 中被繪製的。
咱們使用 map()
方法建立一個新的 React 元素數組
<ul> {["first", "second"].map(item => ( <li>{item}</li> ))} </ul>
這和使用字面量數組是同樣的。
<ul>{[<li>first</li>, <li>second</li>]}</ul>
這個模式能夠聯合解構、JSX 屬性擴散以及其它組件一塊兒使用,看起來簡潔無比
<ul> {arrayOfMessageObjects.map(({ id, ...message }) => ( <Message key={id} {...message} /> ))} </ul>
React 組件不支持函數類型的子元素。
然而 渲染屬性 是一種能夠建立組件並以函數做爲子元素的模式。
這裏有個組件,使用了一個渲染回調函數 children。
這樣寫並無什麼用,可是能夠作爲入門的簡單例子。
const Width = ({ children }) => children(500);
組件把 children 作爲函數調用,同時還能夠傳一些參數。上面這個 500
就是實參。
爲了使用這個組件,咱們能夠在調用組件的時候傳入一個子元素,這個子元素就是一個函數。
<Width>{width => <div>window is {width}</div>}</Width>
咱們能夠獲得下面的輸出。
<div>window is 500</div>
有了這個組件,咱們就能夠用它來作渲染策略。
<Width> {width => (width > 600 ? <div>min-width requirement met!</div> : null)} </Width>
若是有更復雜的條件判斷,咱們可使用這個組件來封裝另一個新組件來利用原來的邏輯。
const MinWidth = ({ width: minWidth, children }) => ( <Width>{width => (width > minWidth ? children : null)}</Width> );
顯然,一個靜態的 Width
組件並無什麼用處,可是給它綁定一些瀏覽器事件就不同了。下面有個實現的例子。
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); } }
許多開發人員都喜歡 高階組件 來實現這種功能。但這只是我的喜愛問題。
你可能會建立一個組件,這個組件會使用 context
而且渲染它的子元素。
class SomeContextProvider extends React.Component { getChildContext() { return { some: "context" }; } render() { // 若是能直接返回 `children` 就完美了 } }
你將面臨一個選擇。把 children
包在一個 div 中並返回,或者直接返回 children
。第一種狀況須要要你添加額外的標記(這可能會影響到你的樣式)。第二種將產生一個沒什麼用處的錯誤。
// option 1: extra div return <div>{children}</div>; // option 2: unhelpful errors return children;
最好把 children
作爲一種不透明的數據類型對待。React 提供了 React.Children
方法來處理 children
。
return React.Children.only(this.props.children);
(我並不肯定這個名字的準確叫法 譯:代理、中介、裝飾?
)
按鈕在 web 應用中隨處可見。而且全部的按鈕都須要一個 type="button"
的屬性。
<button type="button">
重複的寫這些屬性很容易出錯。咱們能夠寫一個高層組件來代理 props
到底層組件。
const Button = props => <button type="button" {...props}>
咱們可使用 Button
組件代替 button
元素,並確保 type
屬性始終是 button。
<Button /> // <button type="button"><button> <Button className="CTA">Send Money</Button> // <button type="button" class="CTA">Send Money</button>
這也是一種 代理組件,用來處理樣式。
假如咱們有一個按鈕,它使用了「primary」作爲樣式類。
<button type="button" className="btn btn-primary">
咱們使用一些單一功能組件來生成上面的結構。
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} /> );
能夠可視化的展現成下面的樣子。
PrimaryBtn() ↳ Btn({primary: true}) ↳ Button({className: "btn btn-primary"}, type: "button"}) ↳ '<button type="button" class="btn btn-primary"></button>'
使用這些組件,下面的這幾種方式會獲得一致的結果。
<PrimaryBtn /> <Btn primary /> <button type="button" className="btn btn-primary" />
這對於樣式維護來講是很是好的。它將樣式的全部關注點分離到單個組件上。
當咱們在寫事件處理函數的時候,一般會使用 handle{事件名字}
的命名方式。
handleClick(e) { /* do something */ }
當須要添加不少事件處理函數的時候,這些函數名字會顯得很重複。這些函數的名字並無什麼價值,由於它們只代理了一些動做或者函數。
handleClick() { require("./actions/doStuff")(/* action stuff */) } handleMouseEnter() { this.setState({ hovered: true }) } handleMouseLeave() { this.setState({ hovered: false }) }
能夠考慮寫一個事件處理函數來根據不一樣的 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}"`) } }
另外,對於簡單的組件,你能夠在組件中使用箭頭函數直接調用導入的動做或者函數
<div onClick={() => someImportedAction({ action: "DO_STUFF" })}
在遇到性能問題以前,不要擔憂性能優化。真的不要
佈局組件表現爲一些靜態 DOM 元素的形式。它們通常並不須要常常更新。
就像下面的這個組件同樣,兩邊各自渲染了一個 children。
<HorizontalSplit leftSide={<SomeSmartComponent />} rightSide={<AnotherSmartComponent />} />
咱們能夠優化這個組件。
HorizontalSplit 組件是兩個子組件的父元素,咱們能夠告訴組件永遠都不要更新
class HorizontalSplit extends React.Component { shouldComponentUpdate() { return false; } render() { <FlexContainer> <div>{this.props.leftSide}</div> <div>{this.props.rightSide}</div> </FlexContainer> } }
「容器用來獲取數據而後渲染到子組件上,僅僅如此。」—Jason Bonta
這有一個 CommentList
組件。
const CommentList = ({ comments }) => ( <ul> {comments.map(comment => ( <li> {comment.body}-{comment.author} </li> ))} </ul> );
咱們能夠建立一個新組件來負責獲取數據渲染到上面的 CommentList
函數組件中。
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} /> } }
對於不一樣的應用上下文,咱們能夠寫不一樣的容器組件。
高階函數 是至少知足下列一個條件的函數:
因此高階組件又是什麼呢?
若是你已經用過 容器組件, 這僅僅是一些泛化的組件, 包裹在一個函數中。
讓咱們以 Greeting
組件開始
const Greeting = ({ name }) => { if (!name) { return <div>鏈接中...</div>; } return <div>Hi {name}!</div>; };
若是 props.name
存在,組件會渲染這個值。不然將展現「鏈接中...」。如今來添加點高階的感受
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} />; } };
這是一個返回了入參爲組件的普通函數
接着,咱們須要把 Greeting
包裹到 Connect
中
const ConnectedMyComponent = Connect(Greeting);
這是一個強大的模式,它能夠用來獲取數據和給定數據到任意 函數組件 中。
函數組件 沒有狀態 (就像名字暗示的同樣)。
事件是狀態的變化。
它們的數據須要傳遞給狀態化的父 容器組件
這就是所謂的「狀態提高」。
它是經過將回調從容器組件傳遞給子組件來完成的
class NameContainer extends React.Component { render() { return <Name onChange={newName => alert(newName)} />; } } const Name = ({ onChange }) => ( <input onChange={e => onChange(e.target.value)} /> );
Name
組件從 NameContainer
組件中接收 onChange
回調,並在 input 值變化的時候調用。
上面的 alert
調用只是一個簡單的演示,但它並無改變狀態
讓咱們來改變 NameContainer
組件的內部狀態。
class NameContainer extends React.Component { constructor() { super(); this.state = { name: "" }; } render() { return <Name onChange={newName => this.setState({ name: newName })} />; } }
這個狀態 被提高 到了容器中,經過添加回調函數,回調中能夠更新本地狀態。這就設置了一個很清晰邊界,而且使功能組件的可重用性最大化。
這個模式並不限於函數組件。由於函數組件沒有生命週期事件,你也能夠在類組件中使用這種模式。
受控輸入 是一種與狀態提高同時使用時很重要的模式
(最好是在一個狀態化的組件上處理事件對象)
討論受控輸入的抽象並不容易。讓咱們以一個不受控的(一般)輸入開始。
<input type="text" />
當你在瀏覽器中調整此輸入時,你會看到你的更改。 這個是正常的
受控的輸入不容許 DOM 變動,這使得這個模式成爲可能。經過在組件範圍中設置值而不是直接在 DOM 範圍中修改
<input type="text" value="This won't change. Try it." />
顯示靜態的輸入框值對於用戶來講並無什麼用處。因此,咱們從狀態中傳遞一個值到 input 上。
class ControlledNameInput extends React.Component { constructor() { super(); this.state = { name: "" }; } render() { return <input type="text" value={this.state.name} />; } }
而後當你改變組件的狀態的時候 input 的值就自動改變了。
return ( <input value={this.state.name} onChange={e => this.setState({ name: e.target.value })} /> );
這是一個受控的輸入框。它只會在咱們的組件狀態發生變化的時候更新 DOM。這在建立一致 UI 界面的時候很是有用。