一.Class Component
Class は間違いなく最も広く使用されている React コンポーネント形式です。例えば:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
最も完全なライフサイクルサポートを持ち、新旧合わせて合計 13 個あります:
// Mounting
constructor()
render()
UNSAFE_componentWillMount()
componentDidMount()
// Updating
static getDerivedStateFromProps()
shouldComponentUpdate()
getSnapshotBeforeUpdate()
UNSAFE_componentWillReceiveProps()
UNSAFE_componentWillUpdate()
componentDidUpdate()
// Unmounting
componentWillUnmount()
// Error Handling
static getDerivedStateFromError()
componentDidCatch()
さらに、State、Props、Context、Ref などの特性もあります。これらのサポートにより、Class は完全なコンポーネント特性を持つ唯一の選択肢 になりました。Class にも多くの問題が存在する にもかかわらず、それは代替不可能です
P.S. 各ライフサイクルの意味とその作用については、React | 黯羽軽揚 を参照
二.Function Component
もう 1 つのコンポーネント形式は関数で、Props を入力し、React Element を出力します。例えば:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
最もシンプルな React コンポーネント形式として:
The simplest way to define a component is to write a JavaScript function.
ライフサイクルさえ持たないほどシンプル で、State は言うまでもありません。これらの制限により、関数型コンポーネントは非常にシンプルな View Component としてのみ使用でき、重任を担えません。かなり長い間、「教育」用途でのみ使用されていました:
Classes have some additional features that we will discuss in the next sections. Until then, we will use function components for their conciseness.
React 16 以降、関数型コンポーネントが段階的に強化されました:
-
createRef/forwardRef: React 16.3 以降、関数型コンポーネントが Ref をサポート
-
[React.memo](/articles/react-16-6 新 api/#articleHeader2): React 16.6 以降、関数型コンポーネントも「shouldComponentUpdate」を迎えました
もちろん、最も重要な強化は自然に Hooks です:
Hooks により、関数型コンポーネントも State、ライフサイクルなどの Class コンポーネント特性(state、lifecycle、context、ref など)を持つことができます
P.S. Hooks に関する詳細情報は、React Hooks 简介 を参照
三.Function Component with Hooks
簡単に言えば、Hooks 有了之后、関数型コンポーネントは Class コンポーネントとほぼ同じ表現力を持つようになります。様々なライフサイクル、State などを含みます
If you write a function component and realize you need to add some state to it, previously you had to convert it to a class. Now you can use a Hook inside the existing function component.
例えば:
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = React.useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useState Hook により、関数型コンポーネントが State を持つようになりました。同様に、ライフサイクル、Context、Ref、コンポーネントインスタンス属性などの特性も同様の Hook 方式でサポートが提供されています。詳細は以下の通り:
| Hook | 特性 | Class との比較 |
|---|---|---|
| useState | State | this.statethis.setState(newState) |
| useEffect | ライフサイクル | componentDidMountcomponentDidUpdate |
| useContext | Context | this.context |
| useReducer | State | Redux Reducer 式の State 管理 |
| useCallback | Function Props | this.myMethod.bind(this) |
| useMemo | パフォーマンス最適化 | 重複計算を回避 |
| useRef | Ref | createRef |
| useImperativeHandle | コンポーネントインスタンス属性/メソッド | forwardRef |
| useLayoutEffect | ライフサイクル | 同期componentDidMount同期 componentDidUpdate |
| useDebugValue | デバッグ | Hooks 状態の可視化(React DevTools からthis.stateを見るのに類似) |
四.Migrate Class to Hooks
もちろん、既存の Class コンポーネントを Hooks にリファクタリングする必要もなければ、也不可能です:
There is no rush to migrate to Hooks. We recommend avoiding any "big rewrites", especially for existing, complex class components.
We intend for Hooks to cover all existing use cases for classes, but we will keep supporting class components for the foreseeable future. At Facebook, we have tens of thousands of components written as classes, and we have absolutely no plans to rewrite them.
ここでは Hooks と Class 特性の対応関係について述べるだけで、この類比は Hooks の理解に役立ちます
constructor()
コンストラクタで最も重要な操作は this.state の宣言/初期化で、State Hook を通じて完了します:
class Example extends React.Component {
constructor() {
this.state = {
count: 0
};
}
}
以下と等価:
function Example() {
// 初期値 0 の state 変数を宣言
const [count, setCount] = React.useState(0);
}
その構文形式は:
const [state, setState] = useState(initialState);
その中で const [state, setState] = xxx は分割代入構文です(詳細は [destructuring(分割代入)_ES6 ノート 5](/articles/destructuring(分割代入)-es6 ノート 5/) を参照)。以下と等価:
const stateVariable = useState(initialState); // ペアを返す
const state = stateVariable[0]; // ペアの最初の項目
const setState = stateVariable[1]; // ペアの 2 番目の項目
状態値(state)と対応する Setter(setState)を返します。Setter を呼び出すとコンポーネントの更新がトリガーされます(Class 内の this.setState に類似)
初期値 initialState は初回レンダリングにのみ作用し(戻り値 state を通じて取得)、その後 state は更新を維持します
特殊的、複数の状態変数が必要な場合、useState を複数回呼び出します:
function ExampleWithManyStates() {
// 複数の状態変数を宣言!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
}
もちろん、状態値はオブジェクトまたは配列也可以是ですが、this.setState() のように merge は行われません:
Unlike this.setState in a class, updating a state variable always replaces it instead of merging it.
例えば:
function ExampleWithManyStates() {
const [profile, setProfile] = useState({
age: 42,
favorite: 'banana',
todos: [{ text: 'Learn Hooks' }]
});
setProfile({
todos: []
});
// Class 中の以下と同等
this.setState({
age: undefined,
favorite: undefined,
todos: []
});
// ではなく
this.setState({
todos: []
});
}
render()
関数型コンポーネント自体が render() 関数で、Props、State などのデータをビューに注入し、イベント処理ロジックを登録します:
class Example extends React.Component {
/* state 初期化部分を省略 */
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
以下と等価:
function Example() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Class コンポーネントと異なり、関数型コンポーネントの State 値は State Hook を通じて取得します(上例中の count)。this.state ではありません。相应的、this.setState() も useState() が返す Setter を通じて完了します
UNSAFE_componentWillMount()
初回レンダリング時に render() の前にトリガーされ、constructor() と機能が少し重複しています。前述の constructor() 部分を参照してください
componentDidMount()
componentDidMount には通常、副作用を伴う操作が含まれます。関数型コンポーネントでは Effect Hook で代替できます:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
/* render 部分を省略 */
}
以下と等価:
function Example() {
const [count, setCount] = useState(0);
// componentDidMount と componentDidUpdate に類似:
useEffect(() => {
// ブラウザ API を使用してドキュメントタイトルを更新
document.title = `You clicked ${count} times`;
});
/* return 部分を省略 */
}
Effect Hook はコンポーネントの每次レンダリング終了時にトリガーされるため、Class コンポーネントの componentDidMount、componentDidUpdate、componentWillUnmount に相当します
構文形式は:
useEffect(didUpdate);
コンポーネントが每次(初回を含む) レンダリング後に何かを行う必要があることを示します:
The function passed to useEffect will run after the render is committed to the screen.
mounting と updating(componentDidMount と componentDidUpdate)を区別する必要がある場合、依存関係を宣言することで完了できます。詳細は Tip: Optimizing Performance by Skipping Effects を参照
実行/クリーンアップを 1 回だけ行う必要がある副作用の場合、それがコンポーネントの状態に依存しないことを宣言します(useEffect(didUpdate, []))。此時 componentDidMount に componentWillUnmount を加えたものと等価になります
しかし、Fiber スケジュールメカニズム により、Effect Hook は同期トリガーではありません。したがって DOM 状態を読み取る必要がある場合、同期の LayoutEffect Hook を使用します
P.S. したがって、厳密に言えば、LayoutEffect Hook が componentDidMount、componentDidUpdate などのライフサイクルと等価な Hooks API です。しかしパフォーマンス/ユーザー体験の考慮から、Effect Hook を優先して使用することを推奨します
特殊的、対応するクリーンアップ作業が必要な副作用があります。例えば外部データソースの購読をキャンセル(メモリリークを回避):
class FriendStatus extends React.Component {
/* state 初期化部分を省略 */
componentDidMount() {
// 購読
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
// 購読キャンセル
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
/* render 部分、および handleStatusChange を省略 */
}
以下と等価:
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// 購読
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// 購読キャンセル
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
上例の通り、Effect Hook は Disposable メカニズムを提供してクリーンアップ操作をサポートしますが、Hooks の运行机制により每次 render 後にクリーンアップ作業がトリガー されます:
Effects run for every render and not just once. This is why React also cleans up effects from the previous render before running the effects next time.
繰り返し購読がパフォーマンスに影響を与える場合、同様に依存関係を宣言することで解決できます(将来的にはコンパイル時に自動的に依存関係を見つける可能性があります)
さらに、複数回 useState() するのと同様に、複数回 useEffect() を使用して異なる Effect を分離できます:
Just like you can use the State Hook more than once, you can also use several effects. This lets us separate unrelated logic into different effects.
例えば:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
// DOM 操作 Effect
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
// データ購読 Effect
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
static getDerivedStateFromProps()
getDerivedStateFromPropsはcomponentWillReceivePropsを代替するために使用され、state が props の変化に関連する必要があるシナリオに対応します
([二。getDerivedStateFromProps をどのように理解するか](/articles/从 componentwillreceiveprops 说起/#articleHeader2) から抜粋)
関数型コンポーネントでは、props の変化が state の変化を引き起こすシナリオは、State Hook を通じて直接完了できます。例えばスクロール方向の記録:
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
lastRow: null,
};
static getDerivedStateFromProps(props, state) {
if (props.currentRow !== state.lastRow) {
return {
isScrollingDown: props.currentRow > state.lastRow,
lastRow: props.currentRow,
};
}
// state に変更がないことを示すために null を返す
return null;
}
}
以下と等価:
function ScrollView({row}) {
let [isScrollingDown, setIsScrollingDown] = useState(false);
let [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// 最後のレンダリング以降に行が変更されました。isScrollingDown を更新。
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
shouldComponentUpdate()
関数型コンポーネントでは、[React.memo](/articles/react-16-6 新 api/#articleHeader2) で代替
getSnapshotBeforeUpdate()
暫時(2019/06/23)代替可能な Hooks API はありませんが、すぐに追加されます
UNSAFE_componentWillReceiveProps()
前述の通り、componentWillReceiveProps と getDerivedStateFromProps はどちらも State Hook で代替。constructor() 部分を参照
UNSAFE_componentWillUpdate()
componentWillUpdate は一般に componentDidUpdate で代替できます。DOM 状態を読み取る必要がある場合、getSnapshotBeforeUpdate で代替:
Typically, this method can be replaced by componentDidUpdate(). If you were reading from the DOM in this method (e.g. to save a scroll position), you can move that logic to getSnapshotBeforeUpdate().
したがって、componentWillUpdate は一般に Effect Hook または LayoutEffect Hook で代替できます。componentDidMount() 部分を参照
componentDidUpdate()
前述の通り、componentDidUpdate は Effect Hook で代替できます。componentDidMount() 部分を参照
componentWillUnmount()
前述の通り、componentWillUnmount は Effect Hook で代替できます。componentDidMount() 部分を参照
static getDerivedStateFromError()
暫時(2019/06/23)代替可能な Hooks API はありませんが、すぐに追加されます
componentDidCatch()
暫時(2019/06/23)代替可能な Hooks API はありませんが、すぐに追加されます
Context
関数型コンポーネントでも Context にアクセスでき、読み取り方式がよりシンプルです:
// 宣言
const {Provider, Consumer} = React.createContext(defaultValue);
// 書き込み
<Provider value={/* some value */}>
// 読み取り
<Consumer>
{value => /* context 値に基づいて何かをレンダリング */}
</Consumer>
以下と等価:
// 宣言
const MyContext = React.createContext(defaultValue);
const Provider = MyContext.Provider;
// 書き込み
<Provider value={/* some value */}>
// 読み取り
const value = useContext(MyContext);
Ref
Ref も同様のサポートを提供:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
}
以下と等価:
function MyComponent() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` はマウントされたテキスト入力要素を指す
inputEl.current.focus();
};
return <input type="text" ref={inputRef} />;
}
つまり:
const refContainer = useRef(initialValue);
// 以下と等価
const refContainer = React.createRef();
Instance Variables
興味深いことに、Ref はコンポーネントインスタンス状態を保持 するためにも使用できます(this.xxx に相当)。例えば:
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
}
さらに進んで、this.mounted を通じて componentDidUpdate を実装できます:
function FunctionComponent(props) {
// 厳密な Update ライフサイクル用のフラグ
const mounted = useRef();
useEffect(() => {
if (mounted.current) {
// componentDidUpdate
}
});
useEffect(() => {
mounted.current = true;
}, []);
// ...
}
Instance Method
Ref を通じて Class コンポーネントインスタンスを参照し、そのインスタンスメソッドにアクセスできます:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
// インスタンスメソッド
focus() {
this.inputRef.current.focus();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
}
class App extends React.Component {
componentDidMount() {
this.myComponent.focus();
}
render() {
return <MyComponent ref={ins => this.myComponent = ins} />;
}
}
以下と等価:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
class App extends React.Component {
componentDidMount() {
this.myComponent.focus();
}
render() {
return <FancyInput ref={ins => this.myComponent = ins} />;
}
}
コメントはまだありません