React官網高級指引模塊知識點整理(二)

react版本:16.13.1

無障礙

  1. for 在 JSX 中應該被寫作 htmlFor:

    <label htmlFor="namedInput">Name:</label>
    <input id="namedInput" type="text" name="name"/>
    
  2. 語義化的 HTML 是無障礙輔助功能網絡應用的基礎。 利用多種 HTML 元素來強化您網站中的信息通常可以使您直接獲得無障礙輔助功能。語義化的 HTML可以是外部設備獲得更好的體驗,比如鍵盤、屏幕朗讀器。

  3. 確保任何可以使用鼠標和指針完成的功能也可以只通過鍵盤完成。只依靠指針會產生很多使鍵盤用戶無法使用你的應用的情況。可以使用onBlur 和 onFocus來達到目的。

代碼分割

1. 動態 import() 語法

//使用之前:
import { add } from './math';
console.log(add(16, 26));

//使用之後:
//當 Webpack 解析到該語法時,會自動進行代碼分割。
import("./math").then(math => {
  console.log(math.add(16, 26));
});

2. React.lazy
React.lazy 函數能讓你像渲染常規組件一樣處理動態引入(的組件)。

React.lazy 接受一個函數,這個函數需要動態調用 import()。它必須返回一個 Promise,該 Promise 需要 resolve 一個 defalut export 的 React 組件。

//使用之前:
import OtherComponent from './OtherComponent';

//使用之後:
const OtherComponent = React.lazy(() => import('./OtherComponent'));
import React, { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

fallback 屬性接受任何在組件加載過程中你想展示的 React 元素。
3. 基於路由的代碼分割

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

4.命名導出
React.lazy 目前只支持默認導出(default exports)。如果你想被引入的模塊使用命名導出(named exports),你可以創建一箇中間模塊,來重新導出爲默認模塊。這能保證 tree shaking 不會出錯,並且不必引入不需要的組件。

// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));

Context

Context 設計目的是爲了共享那些對於一個組件樹而言是“全局”的數據,可以避免通過中間元素傳遞 props。Context 主要應用場景在於很多不同層級的組件需要訪問同樣一些的數據。請謹慎使用,因爲這會使得組件的複用性變差。

// Context 可以讓我們無須明確地傳遍每一個組件,就能將值深入傳遞進組件樹。
// 爲當前的 theme 創建一個 context(“light”爲默認值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    // 使用一個 Provider 來將當前的 theme 傳遞給以下的組件樹。
    // 無論多深,任何組件都能讀取這個值。
    // 在這個例子中,我們將 “dark” 作爲當前的值傳遞下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間的組件再也不必指明往下傳遞 theme 了。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 讀取當前的 theme context。
  // React 會往上找到最近的 theme Provider,然後使用它的值。
  // 在這個例子中,當前的 theme 值爲 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

組件間傳值:

  1. 使用props
  2. 使用Context
  3. 使用組件組合(component composition):傳遞組件自身

API
React.createContext

const MyContext = React.createContext(defaultValue);

只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。這有助於在不使用 Provider 包裝組件的情況下對組件進行測試。注意:將 undefined 傳遞給 Provider 的 value 時,消費組件的 defaultValue 不會生效。

Context.Provider

<MyContext.Provider value={/* 某個值 */}>

Provider 接收一個 value 屬性,傳遞給消費組件。一個 Provider 可以和多個消費組件有對應關係。多個 Provider 也可以嵌套使用,裏層的會覆蓋外層的數據。

當 Provider 的 value 值發生變化時,它內部的所有消費組件都會重新渲染。Provider 及其內部 consumer 組件都不受制於 shouldComponentUpdate 函數。

通過新舊值檢測來確定變化,使用了與 Object.is 相同的算法。

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context;
    /* 在組件掛載完成後,使用 MyContext 組件的值來執行一些有副作用的操作 */
  }
  componentDidUpdate() {
    let value = this.context;
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context;
    /* ... */
  }
  render() {
    let value = this.context;
    /* 基於 MyContext 組件的值進行渲染 */
  }
}
MyClass.contextType = MyContext;

掛載在 class 上的 contextType 屬性會被重賦值爲一個由 React.createContext() 創建的 Context 對象。這能讓你使用 this.context 來消費最近 Context 上的那個值。你可以在任何生命週期中訪問到它,包括 render 函數中。你可以使用 static 這個類屬性來初始化你的 contextType。

Context.Consumer

<MyContext.Consumer>
  {value => /* 基於 context 值進行渲染*/}
</MyContext.Consumer>

相當於Context.Provider。
這需要函數作爲子元素(function as a child)這種做法。這個函數接收當前的 context 值,返回一個 React 節點。如果沒有對應的 Provider,value 參數等同於傳遞給 createContext() 的 defaultValue。

錯誤邊界

錯誤邊界是一種 React 組件,這種組件可以捕獲並打印發生在其子組件樹任何位置的 JavaScript 錯誤,並且,它會渲染出備用 UI,而不是渲染那些崩潰了的子組件樹。錯誤邊界在渲染期間、生命週期方法和整個組件樹的構造函數中捕獲錯誤。

如果一個 class 組件中定義了 static getDerivedStateFromError()componentDidCatch()這兩個生命週期方法中的任意一個(或兩個)時,那麼它就變成一個錯誤邊界。當拋出錯誤後,請使用 static getDerivedStateFromError() 渲染備用 UI ,使用 componentDidCatch() 打印錯誤信息。

錯誤邊界僅可以捕獲其子組件的錯誤,它無法捕獲其自身的錯誤。如果一個錯誤邊界無法渲染錯誤信息,則錯誤會冒泡至最近的上層錯誤邊界

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染能夠顯示降級後的 UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // 你同樣可以將錯誤日誌上報給服務器
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // 你可以自定義降級後的 UI 並渲染
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

Refs 轉發

  • 當ref作用於html元素時,構造函數中使用React.createRef()創建的ref接收底層DOM元素作爲其current屬性的值。
  • 當ref作用於class申明的React組件時,構造函數中使用React.createRef()創建的ref接收組件實例作爲其current屬性的值。
  • 函數組件由於沒有實例,所以不能給其添加ref屬性

ref有三種方式創建:

  1. 通過React.createRef()創建

  2. 回調函數創建

    <CallBackRef ref={ el => {this.componentRef = el}} />
    
  3. 字符串創建
    當組件插入到 DOM 後,ref屬性添加一個組件的引用於到 this.refs,通過this.refs.xxx獲取對節點的引用
    在React16.3之後的版本中該方法已經棄用,建議使用前兩種。

在下面的示例中,FancyButton 使用 React.forwardRef 來獲取傳遞給它的 ref,然後轉發到它渲染的 DOM button:

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

// 你可以直接獲取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

這樣,使用 FancyButton 的組件可以獲取底層 DOM 節點 button 的 ref 。可以在高階組件中使用React.forwardRef ,來獲取被包裹的組件。
ref 不是 prop 屬性。就像 key 一樣,其被 React 進行了特殊處理。

Fragments

React 中的一個常見模式是一個組件返回多個元素。Fragments 允許你將子列表分組,而無需向 DOM 添加額外節點。

class Columns extends React.Component {
  render() {
    return (
      <React.Fragment>
        <td>Hello</td>
        <td>World</td>
      </React.Fragment>
    );
  }
}
//短語法,它不支持 key 或屬性。
class Columns extends React.Component {
  render() {
    return (
      <>
        <td>Hello</td>
        <td>World</td>
      </>
    );
  }
}

key 是唯一可以傳遞給 Fragment 的屬性

高階組件(HOC)

高階組件是參數爲組件,返回值爲新組件的函數。

HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 通過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有副作用

const CommentListWithSubscription = withSubscription(
  CommentList,
  (DataSource) => DataSource.getComments()
);

const BlogPostWithSubscription = withSubscription(
  BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id)
);

// 此函數接收一個組件...
function withSubscription(WrappedComponent, selectData) {
  // ...並返回另一個組件...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: selectData(DataSource, props)
      };
    }
    render() {
      // ... 並使用新數據渲染被包裝的組件!
      // 請注意,我們可能還會傳遞其他屬性
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

深入JSX

1.JSX 僅僅只是 React.createElement(component, props, …children) 函數的語法糖

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>
上面的代碼會被編譯爲:
React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)


<div className="sidebar" />
會編譯爲:
React.createElement(
  'div',
  {className: 'sidebar'}
)

2.由於 JSX 會編譯爲 React.createElement 調用形式,所以 React 庫也必須包含在 JSX 代碼作用域內。所以用JSX必須引入React。

3.在 JSX 類型中使用點語法

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;
}

4.用戶定義的組件必須以大寫字母開頭

// 錯誤!組件應該以大寫字母開頭:
function hello(props) {
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 錯誤!React 會認爲 <hello /> 是一個 HTML 標籤,因爲它沒有以大寫字母開頭:
  return <hello toWhat="World" />;
}


可以寫成下面這樣:
// 正確!組件需要以大寫字母開頭:
function Hello(props) {
  // 正確! 這種 <div> 的使用是合法的,因爲 div 是一個有效的 HTML 標籤:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 正確!React 知道 <Hello /> 是一個組件,因爲它是大寫字母開頭的:
  return <Hello toWhat="World" />;
}

5.你不能將通用表達式作爲 React 元素類型

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 錯誤!JSX 類型不能是一個表達式。
  return <components[props.storyType] story={props.story} />;
}

function Story(props) {
  // 正確!JSX 類型可以是大寫字母開頭的變量。
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

6.if 語句以及 for 循環不是 JavaScript 表達式,所以不能在 JSX 中直接使用。但是,你可以用在 JSX 以外的代碼中

function NumberDescriber(props) {
  let description;
  if (props.number % 2 == 0) {
    description = <strong>even</strong>;
  } else {
    description = <i>odd</i>;
  }
  return <div>{props.number} is an {description} number</div>;
}

7.字符串字面量
當你將字符串字面量賦值給 prop 時,它的值是未轉義的。所以,以下兩個 JSX 表達式是等價的:

<MyComponent message="&lt;3" />

<MyComponent message={'<3'} />

8.如果你沒給 prop 賦值,它的默認值是 true。以下兩個 JSX 表達式是等價的:

<MyTextBox autocomplete />

<MyTextBox autocomplete={true} />

9.JSX 會移除行首尾的空格以及空行。與標籤相鄰的空行均會被刪除,文本字符串之間的新行會被壓縮爲一個空格。因此以下的幾種方式都是等價的:

<div>Hello World</div>

<div>
  Hello World
</div>

<div>
  Hello
  World
</div>

<div>

  Hello World
</div>
 

10.JSX 子元素
子元素允許由多個 JSX 元素組成。這對於嵌套組件非常有用:

<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

11.React 組件也能夠返回存儲在數組中的一組元素:

render() {
  // 不需要用額外的元素包裹列表元素!
  return [
    // 不要忘記設置 key :)
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

12.函數作爲子元素
通常,JSX 中的 JavaScript 表達式將會被計算爲字符串、React 元素或者是列表

// 調用子元素回調 numTimes 次,來重複生成組件
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

13.布爾類型、Null 以及 Undefined 將會忽略
false, null, undefined, and true 是合法的子元素。但它們並不會被渲染。以下的 JSX 表達式渲染結果相同:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

**數字 0,仍然會被 React 渲染。**例如,以下代碼並不會像你預期那樣工作,因爲當 props.messages 是空數組時,0 仍然會被渲染:

<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

要解決這個問題,確保 && 之前的表達式總是布爾值:

<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

性能優化

1.使用壓縮後的生產版本。
2.當一個組件的 props 或 state 變更,React 會將最新返回的元素與之前渲染的元素進行對比,以此決定是否有必要更新真實的 DOM。當它們不相同時,React 會更新該 DOM。
3.通過覆蓋生命週期方法 shouldComponentUpdate 來進行提速。該方法會在重新渲染前被觸發。其默認實現總是返回 true,如果你知道在什麼情況下你的組件不需要更新,你可以在 shouldComponentUpdate 中返回 false 來跳過整個渲染過程。其包括該組件的 render 調用以及之後的操作。

shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

4.可以繼承 React.PureComponent 以代替手寫 shouldComponentUpdate()。它用當前與之前 props 和 state 的淺比較覆寫了 shouldComponentUpdate() 的實現。它只進行淺比較,所以當 props 或者 state 某種程度是可變的話,淺比較會有遺漏,那你就不能使用它了。

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

5.PureComponent和Component的主要區別是:例如一個按鈕狀態isShow初始爲false,初始需要執行render方法,點擊這個按鈕會將isShow置位true。第一次點擊後isShow由false變爲true,PureComponent和Component都需要重新執行render,但是以後點擊isShow一直是由true變爲true,PureComponent會將之前和之後狀態做淺比較,都爲true,所以就不會執行render方法了。但是Component還是會執行render方法。就實現了性能上的優化。vue就不需要擔心這方面的問題,因爲vue是基於Object.defineProperty實現雙向綁定的,數據改變,視圖就改變。而react是基於setState手動渲染的,只要執行setState,相關視圖就會做出相應改變,而這個過程並不會判斷狀態是否改變,都會執行render。所以就有了shouldComponentUpdate。通俗的講,vue相當於自動擋,react相當於手動擋。

Protals

Portal 提供了一種將子節點渲染到存在於父組件以外的 DOM 節點的優秀的方案。

ReactDOM.createPortal(child, container)

第一個參數(child)是任何可渲染的 React 子元素,例如一個元素,字符串或 fragment。第二個參數(container)是一個 DOM 元素。

render() {
  // React 並*沒有*創建一個新的 div。它只是把子元素渲染到 `domNode` 中。
  // `domNode` 是一個可以在任何位置的有效 DOM 節點。
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}

用到的常見場景有:是當父組件有 overflow: hidden 或 z-index 樣式時,但你需要子組件能夠在視覺上“跳出”其容器。例如,對話框、懸浮卡以及提示框。

2.儘管 portal 可以被放置在 DOM 樹中的任何地方,但在任何其他方面,其行爲和普通的 React 子節點行爲一致。由於 portal 仍存在於 React 樹, 且與 DOM 樹 中的位置無關,那麼無論其子節點是否是 portal,像 context 這樣的功能特性都是不變的。

這包含事件冒泡。一個從 portal 內部觸發的事件會一直冒泡至包含 React 樹的祖先,即便這些元素並不是 DOM 樹 中的祖先。就是說事件冒泡還是按照原來的DOM冒泡,而不是按照新插入的DOM冒泡。

Profiler API

Profiler 測量渲染一個 React 應用多久渲染一次以及渲染一次的“代價”。 它的目的是識別出應用中渲染較慢的部分

Profiling 增加了額外的開支,所以它在生產構建中會被禁用。

Profiler 能添加在 React 樹中的任何地方來測量樹中這部分渲染所帶來的開銷。 它需要兩個 prop :一個是 id(string),一個是當組件樹中的組件“提交”更新的時候被React調用的回調函數 onRender(function)。

render(
  <App>
    <Profiler id="Navigation" onRender={callback}>
      <Navigation {...props} />
    </Profiler>
    <Profiler id="Main" onRender={callback}>
      <Main {...props} />
    </Profiler>
  </App>
);

React 會在 profile 包含的組件樹中任何組件 “提交” 一個更新的時候調用這個函數。 它的參數描述了渲染了什麼和花費了多久。

function onRenderCallback(
  id, // 發生提交的 Profiler 樹的 “id”
  phase, // "mount" (如果組件樹剛加載) 或者 "update" (如果它重渲染了)之一
  actualDuration, // 本次更新 committed 花費的渲染時間
  baseDuration, // 估計不使用 memoization 的情況下渲染整顆子樹需要的時間
  startTime, // 本次更新中 React 開始渲染的時間
  commitTime, // 本次更新中 React committed 的時間
  interactions // 屬於本次更新的 interactions 的集合
) {
  // 合計或記錄渲染時間。。。
}

不使用 ES6

你還未使用過 ES6,你可以使用 create-react-class 模塊:

var createReactClass = require('create-react-class');
var Greeting = createReactClass({
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});

不過與class語法有以下點不同:
1.聲明默認屬性
無論是函數組件還是 class 組件,都擁有 defaultProps 屬性:

class Greeting extends React.Component {
  // ...
}

Greeting.defaultProps = {
  name: 'Mary'
};

使用 createReactClass() 方法創建組件,那就需要在組件中定義 getDefaultProps() 函數:

var Greeting = createReactClass({
  getDefaultProps: function() {
    return {
      name: 'Mary'
    };
  },

  // ...

});

2.初始化 State
使用 ES6 的 class 關鍵字創建組件,你可以通過給 this.state 賦值的方式來定義組件的初始 state:

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: props.initialCount};
  }
  // ...
}

如果使用 createReactClass() 方法創建組件,你需要提供一個單獨的 getInitialState 方法,讓其返回初始 state:

var Counter = createReactClass({
  getInitialState: function() {
    return {count: this.props.initialCount};
  },
  // ...
});

3.自動綁定
ES6 的 class 關鍵字創建的 React 組件,方法不會自動綁定 this 到這個組件實例。 你需要在 constructor 中顯式地調用 .bind(this)。

使用 createReactClass() 方法創建組件,組件中的方法會自動綁定至實例。

4.Mixins
React 中使用 ES6 class 時,將不支持 mixins 。
使用 createReactClass 創建 React 組件的時候,可以引入 mixins功能。

var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  }
};

var createReactClass = require('create-react-class');

var TickTock = createReactClass({
  mixins: [SetIntervalMixin], // 使用 mixin
  componentDidMount: function() {
    this.setInterval(this.tick, 1000); // 調用 mixin 上的方法
  },
  render: function() {
    return (
      <p> </p>
    );
  }
});

ReactDOM.render(
  <TickTock />,
  document.getElementById('example')
);

如果組件擁有多個 mixins,那麼這些生命週期方法都會被調用的。使用 mixins 時,mixins 會先按照定義時的順序執行,最後調用組件上對應的方法。

不使用 JSX 的 React

每個 JSX 元素只是調用 React.createElement(component, props, …children) 的語法糖。因此,使用 JSX 可以完成的任何事情都可以通過純 JavaScript 完成。

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}

ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);

可以編寫爲不使用 JSX 的代碼:

class Hello extends React.Component {
  render() {
    return React.createElement('div', null, `Hello ${this.props.toWhat}`);
  }
}

ReactDOM.render(
  React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);

Render Props

術語 “render prop” 是指一種在 React 組件之間使用一個值爲函數的 prop 共享代碼的簡單技術

具有 render prop 的組件接受一個函數,該函數返回一個 React 元素並調用它而不是實現自己的渲染邏輯。
render prop 是一個用於告知組件需要渲染什麼內容的函數 prop。

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>
class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>

        {/*
          Instead of providing a static representation of what <Mouse> renders,
          use the `render` prop to dynamically determine what to render.
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移動鼠標!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

使用 Props 而非 render
重要的是要記住,render prop 是因爲模式才被稱爲 render prop ,你不一定要用名爲 render 的 prop 來使用這種模式。事實上, 任何被用於告知組件需要渲染什麼內容的函數 prop 在技術上都可以被稱爲 “render prop”.

我們也可以簡單地使用 children prop!

<Mouse>
  {mouse => (
    <p>鼠標的位置是 {mouse.x}{mouse.y}</p>
  )}
</Mouse>

嚴格模式

StrictMode 是一個用來突出顯示應用程序中潛在問題的工具。與 Fragment 一樣,StrictMode 不會渲染任何可見的 UI。它爲其後代元素觸發額外的檢查和警告。

嚴格模式檢查僅在開發模式下運行;它們不會影響生產構建。

import React from 'react';

function ExampleApplication() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}

在上述的示例中,不會對 Header 和 Footer 組件運行嚴格模式檢查。但是,ComponentOne 和 ComponentTwo 以及它們的所有後代元素都將進行檢查。

StrictMode 目前有助於:

  • 識別不安全的生命週期
  • 關於使用過時字符串 ref API 的警告
  • 關於使用廢棄的 findDOMNode 方法的警告
  • 檢測意外的副作用
  • 檢測過時的 context API

在 CSS 中,如果你不希望節點成爲佈局的一部分,則可以使用 display: contents 屬性。

使用 PropTypes 進行類型檢查

自 React v15.5 起,React.PropTypes 已移入另一個包中。請使用 prop-types 庫 代替。
import PropTypes from ‘prop-types’;

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

PropTypes 提供一系列驗證器,可用於確保組件接收到的數據類型是有效的。當傳入的 prop 值類型不正確時,JavaScript 控制檯將會顯示警告。出於性能方面的考慮,propTypes 僅在開發模式下進行檢查。
PropTypes

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 你可以將屬性聲明爲 JS 原生類型,默認情況下
  // 這些屬性都是可選的。
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括數字、字符串、元素或數組)
  // (或 Fragment) 也包含這些類型。
  optionalNode: PropTypes.node,

  // 一個 React 元素。
  optionalElement: PropTypes.element,

  // 一個 React 元素類型(即,MyComponent)。
  optionalElementType: PropTypes.elementType,

  // 你也可以聲明 prop 爲類的實例,這裏使用
  // JS 的 instanceof 操作符。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你可以讓你的 prop 只能是特定的值,指定它爲
  // 枚舉類型。
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 一個對象可以是幾種類型中的任意一個類型
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 可以指定一個數組由某一類型的元素組成
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 可以指定一個對象由某一類型的值組成
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 可以指定一個對象由特定的類型值組成
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),
  
  // An object with warnings on extra properties
  optionalObjectWithStrictShape: PropTypes.exact({
    name: PropTypes.string,
    quantity: PropTypes.number
  }),   

  // 你可以在任何 PropTypes 屬性後面加上 `isRequired` ,確保
  // 這個 prop 沒有被提供時,會打印警告信息。
  requiredFunc: PropTypes.func.isRequired,

  // 任意類型的數據
  requiredAny: PropTypes.any.isRequired,

  // 你可以指定一個自定義驗證器。它在驗證失敗時應返回一個 Error 對象。
  // 請不要使用 `console.warn` 或拋出異常,因爲這在 `onOfType` 中不會起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 你也可以提供一個自定義的 `arrayOf` 或 `objectOf` 驗證器。
  // 它應該在驗證失敗時返回一個 Error 對象。
  // 驗證器將驗證數組或對象中的每個值。驗證器的前兩個參數
  // 第一個是數組或對象本身
  // 第二個是他們當前的鍵。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

非受控組件

在大多數情況下,我們推薦使用 受控組件 來處理表單數據。在一個受控組件中,表單數據是由 React 組件來管理的。另一種替代方案是使用非受控組件,這時表單數據將交由 DOM 節點來處理。

在 React 渲染生命週期時,表單元素上的 value 將會覆蓋 DOM 節點中的值,在非受控組件中,你經常希望 React 能賦予組件一個初始值,但是不去控制後續的更新。 在這種情況下, 你可以指定一個 defaultValue 屬性,而不是 value。

render() {
  return (
    <form onSubmit={this.handleSubmit}>
      <label>
        Name:
        <input
          defaultValue="Bob"
          type="text"
          ref={this.input} />
      </label>
      <input type="submit" value="Submit" />
    </form>
  );
}

同樣,<input type="checkbox"><input type="radio"> 支持 defaultChecked,<select><textarea> 支持 defaultValue。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章