React 模式(中文版)

中文版:reactpatterns.cn/ 原版:reactpatterns.comhtml

函數組件 (Function component)

函數組件 是最簡單的一種聲明可複用組件的方法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"
};
複製代碼

屬性解構 (Destructuring props)

解構賦值 是一種 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 spread attributes)

屬性展開是 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>;
}
複製代碼

合併解構屬性和其它值 (Merge destructured props with other values)

組件就是一種抽象。

好的抽象是能夠擴展的。

好比說下面這個組件使用 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} />; } 複製代碼

條件渲染 (Conditional rendering)

不能夠在一個組件聲明中使用 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>
  );
}
複製代碼

子元素類型 (Children types)

不少類型均可以作爲 React 的子元素。

多數狀況下會是 數組 或者 字符串

字符串 String

<div>Hello World!</div>
複製代碼

數組 Array

<div>{["Hello ", <span>World</span>, "!"]}</div>
複製代碼

數組作爲子元素 (Array as children)

將數組作爲子元素是很常見的。

列表是如何在 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>
複製代碼

函數作爲子元素 (Function as children)

React 組件不支持函數類型的子元素。

然而 渲染屬性 是一種能夠建立組件並以函數做爲子元素的模式。

渲染屬性 (Render prop)

這裏有個組件,使用了一個渲染回調函數 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);
  }
}
複製代碼

許多開發人員都喜歡 高階組件 來實現這種功能。但這只是我的喜愛問題。

子組件的傳遞 (Children pass-through)

你可能會建立一個組件,這個組件會使用 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);
複製代碼

代理組件 (Proxy component)

(我並不肯定這個名字的準確叫法 譯:代理、中介、裝飾?)

按鈕在 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>
複製代碼

樣式組件 (Style component)

這也是一種 代理組件,用來處理樣式。

假如咱們有一個按鈕,它使用了「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" />
複製代碼

這對於樣式維護來講是很是好的。它將樣式的全部關注點分離到單個組件上。

組織事件 (Event switch)

當咱們在寫事件處理函數的時候,一般會使用 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" })}
複製代碼

在遇到性能問題以前,不要擔憂性能優化。真的不要

佈局組件 (Layout component)

佈局組件表現爲一些靜態 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>
  }
}
複製代碼

容器組件 (Container component)

「容器用來獲取數據而後渲染到子組件上,僅僅如此。」—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} /> } } 複製代碼

對於不一樣的應用上下文,咱們能夠寫不一樣的容器組件。

高階組件 (Higher-order component)

高階函數 是至少知足下列一個條件的函數:

  • 接受一個或多個函數做爲輸入
  • 輸出一個函數

因此高階組件又是什麼呢?

若是你已經用過 容器組件, 這僅僅是一些泛化的組件, 包裹在一個函數中。

讓咱們以 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);
複製代碼

這是一個強大的模式,它能夠用來獲取數據和給定數據到任意 函數組件 中。

狀態提高 (State hoisting)

函數組件 沒有狀態 (就像名字暗示的同樣)。

事件是狀態的變化。

它們的數據須要傳遞給狀態化的父 容器組件

這就是所謂的「狀態提高」。

它是經過將回調從容器組件傳遞給子組件來完成的

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 })} />; } } 複製代碼

這個狀態 被提高 到了容器中,經過添加回調函數,回調中能夠更新本地狀態。這就設置了一個很清晰邊界,而且使功能組件的可重用性最大化。

這個模式並不限於函數組件。由於函數組件沒有生命週期事件,你也能夠在類組件中使用這種模式。

受控輸入 是一種與狀態提高同時使用時很重要的模式

(最好是在一個狀態化的組件上處理事件對象)

受控輸入 (Controlled input)

討論受控輸入的抽象並不容易。讓咱們以一個不受控的(一般)輸入開始。

<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 界面的時候很是有用。

若是你使用 函數組件 作爲表單元素,那就得閱讀 狀態提高 一節,把狀態轉移到上層的組件樹上。

相關文章
相關標籤/搜索