初めてReactフックを試してみましたが、データを取得して2つの異なる状態変数(データとロードフラグ)を更新すると、コンポーネント(データテーブル)がレンダリングされることに気付くまで、すべてが良さそうでした状態アップデータへの両方の呼び出しが同じ関数で発生している場合でも、2回。これは、両方の変数をコンポーネントに返すAPI関数です。
const getData = url => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(async () => {
const test = await api.get('/people')
if(test.ok){
setLoading(false);
setData(test.data.results);
}
}, []);
return { data, loading };
};
通常のクラスコンポーネントでは、複雑なオブジェクトになる可能性のある状態を更新するために単一の呼び出しを行いますが、「フックの方法」は状態を小さな単位に分割することのようです。個別に更新されたときにレンダリングします。これを軽減する方法はありますか?
loading
状態とdata
状態を1つの状態オブジェクトに結合してから、1つのsetState
呼び出しを行うと、レンダリングは1つだけになります。
注:クラスコンポーネントのsetState
とは異なり、setState
から返されるuseState
はオブジェクトをマージしません既存の状態では、オブジェクトを完全に置き換えます。マージを行う場合は、以前の状態を読み取り、自分で新しい値とマージする必要があります。 docs を参照してください。
パフォーマンスの問題があると判断するまで、レンダリングを過度に呼び出すことについてあまり心配しません。レンダリング(Reactコンテキスト)と仮想DOM更新の実際のDOMへのコミットは別の問題です。ここでのレンダリングとは、仮想DOMの生成を指し、ブラウザDOMの更新に関するものではありません。 Reactは、setState
呼び出しをバッチ処理し、ブラウザのDOMを最終的な新しい状態に更新します。
const {useState, useEffect} = React;
function App() {
const [userRequest, setUserRequest] = useState({
loading: false,
user: null,
});
useEffect(() => {
// Note that this replaces the entire object and deletes user key!
setUserRequest({ loading: true });
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUserRequest({
loading: false,
user: data.results[0],
});
});
}, []);
const { loading, user } = userRequest;
return (
<div>
{loading && 'Loading...'}
{user && user.name.first}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
const {useState, useEffect} = React;
function useMergeState(initialState) {
const [state, setState] = useState(initialState);
const setMergedState = newState =>
setState(prevState => Object.assign({}, prevState, newState)
);
return [state, setMergedState];
}
function App() {
const [userRequest, setUserRequest] = useMergeState({
loading: false,
user: null,
});
useEffect(() => {
setUserRequest({ loading: true });
fetch('https://randomuser.me/api/')
.then(results => results.json())
.then(data => {
setUserRequest({
loading: false,
user: data.results[0],
});
});
}, []);
const { loading, user } = userRequest;
return (
<div>
{loading && 'Loading...'}
{user && user.name.first}
</div>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
反応フックのバッチ更新https://github.com/facebook/react/issues/14259
ボタンのクリックや入力の変更など、Reactベースのイベント内からトリガーされた場合、Reactは現在、状態の更新をバッチ処理します。非同期呼び出しなど、Reactイベントハンドラーの外部でトリガーされた場合、バッチ更新は行われません。
これには、useReducer
!を使用した別のソリューションもあります。まず、新しいsetState
を定義します。
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{loading: true, data: null}
)
その後、this
なしで、古き良きクラスthis.setState
のように単純に使用できます!
setState({loading: false, data: test.data.results})
したがって、すべて一緒に:
import {useReducer} from 'react'
const getData = url => {
const [state, setState] = useReducer(
(state, newState) => ({...state, ...newState}),
{loading: true, data: null}
)
useEffect(async () => {
const test = await api.get('/people')
if(test.ok){
setState({loading: false, data: test.data.results})
}
}, [])
return state
}
答えに少し追加 https://stackoverflow.com/a/53575023/12114
クール!このフックの使用を計画している人のために、次のように、関数を引数として使用するために少し堅牢な方法で記述することができます。
const useMergedState = initial => {
const [state, setState] = React.useState(initial);
const setMergedState = newState =>
typeof newState == "function"
? setState(prevState => ({ ...prevState, ...newState(prevState) }))
: setState(prevState => ({ ...prevState, ...newState }));
return [state, setMergedState];
};
Update:最適化されたバージョン、着信部分状態が変更されなかった場合、状態は変更されません。
const shallowPartialCompare = (obj, partialObj) =>
Object.keys(partialObj).every(
key =>
obj.hasOwnProperty(key) &&
obj[key] === partialObj[key]
);
const useMergedState = initial => {
const [state, setState] = React.useState(initial);
const setMergedState = newIncomingState =>
setState(prevState => {
const newState =
typeof newIncomingState == "function"
? newIncomingState(prevState)
: newIncomingState;
return shallowPartialCompare(prevState, newState)
? prevState
: { ...prevState, ...newState };
});
return [state, setMergedState];
};
これにより:
const [state, setState] = useState({ username: '', password: ''});
// later
setState({
...state,
username: 'John'
});