ホームページ >ウェブフロントエンド >jsチュートリアル >React: 再利用可能、分離、分離
新しいページで作業するときは、ページ自体にコンポーネントを書き始めるだけではありません。コンポーネントのユニットの個別化を開始して、それぞれが十分に独立し、グローバル状態の変更によって再レンダリングが発生しないようにします。たとえば、このページを作成するタスクが与えられたと想像してください。
私がコードを学習していたときと同じように、これらすべてのコンポーネントを 1 つのファイルに記述したくなるかもしれません。
// WRONG export const LoginPage = () => { const [userId, setUserId] = useState('') const [password, setPassword] = useState('') return ( <div> <div className="d-flex flex-row"> <div id="marketing-container"> <svg {...fbLogo} /> <h2>Connect with friends and the world around you on Facebook</h2> </div> <div id="login-form"> <div> <input id="user-id" value={userId} onChange={(e) => setUserId(e.target.value)} /> <input id="password" value={password} onChange={(e) => setPassword(e.target.value)} /> <button>Log in</button> <a href="/forgot-password">Forgot password</a> <br /> <button>Create new account</button> </div> <div> <strong>Create a page</strong> <span>for a celebrity, brand or business.</span> </div> </div> </div> </div> ) } export default LoginPage
しかし実際には、これらの要素の多くは将来的にアプリ全体で再利用される可能性があり、つまり、書き直すかコピー/ペーストする必要があることになります。明確に定義されたデザインを使用する場合、要素はレゴのように使用される可能性が高く、ログイン ページに表示されるロゴはダッシュボード画面に表示されるロゴと同じになります (もちろんサイズが異なるだけかもしれません)。ユーザー ID の入力も同様ですが、デザイン的にはおそらくユーザー編集ページのものと同じでしょう。
ここで次の点に移りますが、コンポーネントはビジネス ロジックからプレゼンテーション ロジックまで分離する必要があります。これが意味するのは、状態と通信する部分はそれ自体のコンポーネントであるべきであり、このコンポーネントはプレゼンテーション用の小道具をプレゼンテーション用コンポーネントに渡すだけであるということです。
// Presentational component const UserIdInputComponent = ({ value, onChange }) => <input value={value} onChange={onChange} /> // Logic component export const UserIdInput = () => { const [value, setValue] = useState('') const handleChange = (e) => setValue(e.target.value) return <UserIdInputComponent value={value} onChange={handleChange} /> }
これにより、状態管理から切り離されたプレゼンテーションコンポーネントをエクスポートするだけで、ストーリーブックなどのツールが適切に動作できるようになります。 API 呼び出しを行ったり、グローバルな状態を変更したりするロジックの重いコンポーネントを Storybook に統合しなければならないのは煩わしい場合があります。このアプローチを使用すると、さまざまなプロパティに基づいてコンポーネントがどのように変化するかを視覚的に確認できます。
メインページに戻ります。おそらくこれで私がどこに向かっているのかがわかるでしょう。すべてを同じページに書くのではなく。このコンポーネントを再利用する方法、状態から切り離す方法、およびこのコンポーネントに関連する props が変更されない限り再レンダリングされないように分離する方法を考えてください。
export const LoginPage = () => ( <div> <div className="d-flex flex-row"> <FbMarketing /> <LoginForm /> </div> </div> ) export default LoginPage
最良のシナリオでは、これがコーディングの開始方法です。すべてが期待どおりに機能することがわかった後で、戻ってリファクタリングするのはさらに面倒です。私は画面上で何かを手早く見て、不安を和らげ、最初から適切な構造を構築し始めたいという罪を犯しています。
const FbLogo = () => ( <svg {...fbLogoAttributes} /> ) const FbSlogan = () => ( <h2>Connect with friends and the world around you on Facebook.</h2> ) const FbMarketing = () => ( <> <FbLogo /> <FbSlogan /> </> )
現時点では、ここにあるものはすべてプレゼンテーションです。これらは、FbLogoSmall、FbLogoMedium などとしてさらに個別化できます。
今度は、いくつかのロジックを含む部分、つまりログインフォームです。ログインなのかログインなのかログインなのかはわかりませんが、Facebook の用語「ログイン」を使用します。
注意していただきたいのは、各コンポーネントは再利用可能であり、分離され、分離されている必要があります。
再利用可能:
まず、UserIdInput を再利用可能にしてから、このアプローチを他のパスワード入力にコピーします。本番レベルの入力には、テスト ID、props に基づいて変更されるクラス、aria 属性、autofocus などの他の属性が含まれることに注意してください。コードベースが使用するツールに応じて、その他のプロパティ/属性が追加されます。もし誰かが私がここで書いていることよりも複雑だと言うなら、その人の言うことを聞いてください。
// UserIdInput.js import { useContext, createContext } from "react"; export const UserIdContext = createContext(); const UserIdInput = () => { const { userId, setUserId } = useContext(UserIdContext); const handleChange = (e) => { setUserId(e.target.value); }; return <UserIdInputComponent value={userId} onChange={handleChange} />; };
この入力は、たとえばユーザー編集フォームで再利用できるようになりました。パスワード入力は次のようになります:
// PasswordInput.js import { useContext, createContext } from "react"; export const PasswordContext = createContext(); const PasswordInput = () => { const { password, setPassword } = useContext(PasswordContext); const handleChange = (e) => { setPassword(e.target.value); }; return ( <div> <PasswordInputComponent value={password} onChange={handleChange} /> </div> ); };
デカップリング:
「ビジネス ロジック」とビジュアルの、プレゼンテーション部分からロジックを切り離すという意味での切り離し: ここでは、途中で変更や新しい関数定義を行わずに props が渡されていることがわかります。jsx も返しています。 return キーワードなしで真っすぐに。もう一度言いますが、これはもっと複雑だと言われたら、それは…ラベルは独自のコンポーネントである必要があり、入力も同様です。
// UserIdInputComponent.js const UserIdInputComponent = ({ value, onChange }) => ( <div> <label>User Id:</label> <input type="text" value={value} onChange={onChange} required /> </div> );
// PasswordInputComponent.js const PasswordInputComponent = ({ value, onChange }) => ( <div> <label>Password:</label> <input type="password" value={value} onChange={onChange} required /> </div> );
孤立:
コンテキストを作成することで分離された部分をすでに処理しました。これで、入力のいずれかを変更するたびに、他の入力は再レンダリングされなくなります。再レンダリングされる唯一の要素は、変更される入力とログイン ボタンです。これは、React アプリが適切に最適化されているかどうかを示す良い指標であり、時期尚早な最適化が適切な場合もあります。チームのスキルを向上させます。
const LoginButton = () => { const { userId } = useContext(UserIdContext); const { password } = useContext(PasswordContext); const onClick = (e) => { e.preventDefault(); console.log("form submit", userId, password) }; return <button onClick={onClick}>Log in</button>; };
例外です!これは実際には起こりませんでした。コンテキストを使用して変更を分離しようとしましたが、userId とパスワードを共有することになると、redux を使用する必要がありました。UserIdProvider を使用して LoginButton をラップするとすぐに、新しい userId とパスワードで新しい状態が作成されるためです。 。 Redux を使用すると次のようになります。
// LoginButton.js import { useSelector } from "react-redux"; const LoginButton = () => { const { userId, password } = useSelector(state => state) const onClick = (e) => { e.preventDefault(); console.log("form submit", userId, password); }; return <button onClick={onClick}>Log in</button>; }; export default LoginButton
おそらく以前に入力したはずですが、ここに Redux ストアがあります。
// store.js import { createSlice, configureStore } from '@reduxjs/toolkit' const login = createSlice({ name: 'login', initialState: { userId: '', password: '', }, reducers: { userId: (state, action) => { state.userId = action.payload }, password: (state, action) => { state.password = action.payload } } }) export const { userId: setUserId, password: setPassword } = login.actions export const store = configureStore({ reducer: login.reducer })
redux を使用していますが、変更を分離して再レンダリングを最小限に抑えるのに見事に機能します。通常、私は再レンダリングを何としても避ける人をあまり信頼しませんが、それは反応コードが優れていることを示す単なる指標です。
Here are the updated files for the two inputs. Not a lot changed but pay attention to how easy it was for me to change only the business logic component. Changed the value selector, the handleChange function and that was it. This is one of the advantages of decoupling, it’s not that obvious with such a small component but a codebase that uses complex logic I can see how this approach can be beneficial.
// UserIdInput.js (revised final) import { setUserId } from "./store"; import { useDispatch, useSelector } from "react-redux"; const UserIdInputComponent = ({ value, onChange }) => ( <div> <label>User Id:</label> <input type="text" value={value} onChange={onChange} required /> </div> ); const UserIdInput = () => { const userId = useSelector(({ userId }) => userId) const dispatch = useDispatch() const handleChange = (e) => { dispatch(setUserId(e.target.value)) }; return <UserIdInputComponent value={userId} onChange={handleChange} />; };
// PasswordInput.js (revised final) import { useDispatch, useSelector } from "react-redux"; import { setPassword } from "./store"; const PasswordInputComponent = ({ value, onChange }) => ( <> <label>Password:</label> <input type="password" value={value} onChange={onChange} required /> </> ); const PasswordInput = () => { const password = useSelector(({ password }) => password) const dispatch = useDispatch() const handleChange = e => { dispatch(setPassword(e.target.value)) }; return <PasswordInputComponent value={password} onChange={handleChange} /> };
The result should only highlight updates on the changed input and the login button itself like so:
There’s a problem though, the labels are also updating. Let’s fix that really quick just to prove the point of over, but potentially necessary optimization. Up to your discretion.
// UserIdInput.js import { setUserId } from "./store"; import { useDispatch, useSelector } from "react-redux"; const UserIdInputComponent = ({ value, onChange }) => ( <input type="text" value={value} onChange={onChange} required /> ); const UserIdInput = () => { const userId = useSelector(({ userId }) => userId) const dispatch = useDispatch() const handleChange = (e) => { dispatch(setUserId(e.target.value)) }; return <UserIdInputComponent value={userId} onChange={handleChange} />; }; // separated the label from the logic heavy component export const UserIdInputWithLabel = () => ( <div> <label>User id: </label> <UserIdInput /> </div> ) export default UserIdInputWithLabel
Here is the password input.
// PasswordInput.js import { useDispatch, useSelector } from "react-redux"; import { setPassword } from "./store"; const PasswordInputComponent = ({ value, onChange }) => ( <input type="password" value={value} onChange={onChange} required /> ); const PasswordInput = () => { const password = useSelector(({ password }) => password) const dispatch = useDispatch() const handleChange = e => { dispatch(setPassword(e.target.value)) }; return <PasswordInputComponent value={password} onChange={handleChange} /> }; // separated label from logic heavy component const PasswordInputWithLabel = () => ( <div> <label>Password: </label> <PasswordInput /> </div> ) export default PasswordInputWithLabel
This approach yields the following results:
Fully optimized.
Available here: https://github.com/redpanda-bit/reusable-decoupled-isolated
There you have it, reusable, decoupled, and isolated react components. Very small example but hope that it gives you an idea of how production grade react applications look like. It may be disappointing for some to see all the work that goes into creating a good react component, but I’ll tell ya, once you are faced with a huge form that has complex elements and possibly some animation you will see positive gains on speed. The last thing you want is an input lagging behind in front of a 100 words per minute types.
https://nextjs.org/
https://redux.js.org/
以上がReact: 再利用可能、分離、分離の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。