react版本:16.13.1
無障礙
-
for 在 JSX 中應該被寫作 htmlFor:
<label htmlFor="namedInput">Name:</label> <input id="namedInput" type="text" name="name"/>
-
語義化的 HTML 是無障礙輔助功能網絡應用的基礎。 利用多種 HTML 元素來強化您網站中的信息通常可以使您直接獲得無障礙輔助功能。語義化的 HTML可以是外部設備獲得更好的體驗,比如鍵盤、屏幕朗讀器。
-
確保任何可以使用鼠標和指針完成的功能也可以只通過鍵盤完成。只依靠指針會產生很多使鍵盤用戶無法使用你的應用的情況。可以使用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} />;
}
}
組件間傳值:
- 使用props
- 使用Context
- 使用組件組合(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有三種方式創建:
-
通過React.createRef()創建
-
回調函數創建
<CallBackRef ref={ el => {this.componentRef = el}} />
-
字符串創建
當組件插入到 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="<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。