防弾反応コンポーネントを構築する方法



導入
反応は宣言的な枠組みです.これは、次の状態に到達するために何を変更する必要があるかを記述する代わりに、DOMがどのような状態になっているかを記述し、状態間の遷移の仕方を理解してみましょう.
命令型から宣言的な考え方への移行は非常に難しいです、そして、しばしば私がコードでバグまたは非効率性を見つけるとき、それはユーザーが静的な考え方でまだ立ち往生しているので、それです.
このブログの記事では、宣言的な考え方に深く潜り込み、壊れないコンポーネントを構築するためにどのように使うことができますか.

命令型対宣言型:
この例を確認してください.
ボタンをクリックするたびに値がtrue and false . このように命令すると、次のようになります.
toggle.addEventListener("click", () => {
  toggleState = !toggleState;
  // I have to manually update the dom 
  toggle.innerText = `toggle is ${toggleState}`;
});
完全な例here
ここでは宣言コードで書かれたものと同じです.
  const [toggle, setToggle] = useState(false);
  // notice how I never explicitely have to update anything in the dom
  return (
    <button onClick={() => setToggle(!toggle)}>
      toggle is {toggle.toString()}
    </button>
  );
完全な例here
変更するたびにisToggled 最初の例では、DOMを更新するのを覚えておかなければならない.反応すると、コードは“ちょうど”作品.

マインドセット
新しい考え方の中核は以下の引用です.

Your view should be expressed as a pure function of your application state.


または

view = f(application_state)


または

お客様のデータを介して機能が表示され、あなたのビューは、他の端
Responseの関数のコンポーネントは、古いクラスのコンポーネントよりも、この精神的なモデルに非常に近づきます.
これは少し抽象的なので、上からのトグルコンポーネントに適用しましょう.

the "toggle is" button should be expressed as a pure function of the isToggled variable.


or

button = f(isToggled)


or

(これからは数学的な表記に固執しますが、基本的に交換可能です)
この例を拡張しましょう.いつでも言うisToggled is true 私は、ボタンが緑色でなければなりません、さもなければ、それは赤でなければなりません.
一般的な初心者間違いは次のように書くことです.
const [isToggled, setIsToggled] = useState(false);
const [color, setColor] = useState('green');

function handleClick(){
  setIsToggled(!toggle)
  setColor(toggle ? 'green' : 'red')
}

  return (
    <button style={{color}} onClick={handleClick}>
      toggle is {isToggled.toString()}
    </button>
  );
我々が我々の数学表記法でこれを書くならば、我々は得ます

button = f(isToggled, color)


たった今application_state から作られるisToggled and color , しかし、我々が密接に見るならば、我々はそれを見ることができますcolor の関数として表現することができますisToggled

color = f(isToggled)


または実際のコード
const color = isToggled ? 'green' : 'red'
このタイプの変数はしばしばderived state からcolor から派生isToggled )
最後に、このコンポーネントはまだ以下のようになります.

button = f(isToggled)



現実世界でこれを利用する方法
上の例では、それは非常に簡単に重複した状態を発見するには、私たちの数学的な表記法でそれを書くことなく、しかし、我々のアプリはますます複雑に成長すると、それは難しく、すべてのアプリケーションの状態を追跡し、重複をポップアップを開始します.
これの一般的な症状は、多くのrerdersと古い値です.
あなたがロジックの複雑な部分を見るたびに、あなたが持っている状態のすべての可能な部分について考えるために数秒かかる.

dropdown = f(selectedValue, arrowDirection, isOpen, options, placeholder)


次に、すぐに不要な状態を並べ替えることができますて

arrowDirection = f(isOpen) -> arrowDirection can be derived


また、コンポーネントがどのような状態になるのか、どのように小道具として入るのかをソートすることもできます.isOpen 例えば、通常、ドロップダウンの外部からアクセスする必要はありません.
この点から、コンポーネントのAPIはおそらく次のようになります.<dropdown options={[item1, item2]} selectedValue={null} placeholder='Favorite food' /> .
コンポーネントを書く今信じられないほど簡単にはすでに正確にどのように構造化される予定だ知っている.あなたが今把握する必要があるすべては、DOMにあなたの状態をレンダリングする方法です.

もう一つの例

これは一見してもたくさんの状態に見えますが、私たちが密接に見れば、ほとんどの人が派生できることが分かります.isDisabled = f(selectedValue, range) "..." position = f(selectedValue, range) middle fields = f(selectedValue, range) amount of fields = f(selectedValue, range)結局、何が残っているかは

pagination = f(selectedValue, range)


以下は実装です.
それは堅牢で、速くて、読むのが比較的簡単です.
一歩一歩踏み出してルートを変えましょう/${pageNumber} ページが更新されるたびに.
あなたの答えはこんな感じです.
const history = useHistory();
const [page, setPage] = useState(1);

function handleChange(newPage){
  setPage(newPage)
   history.push(`/${newPage}`);
}

useEffect(()=>{
  setPage(history.location.pathname.replace("/", ""))
},[])

  return (
    <div className="App">
      <Pagination value={page} range={12} onChange={handleChange} />
    </div>
  );
それがするならば、私には若干の悪い知らせがあります:あなたは、複製状態を持ちます.

pageNumber = f(window.href)


PageNumberは独自の状態を必要としません.代わりに、状態はURLに格納されます.here はその実装です.

その他の意味
我々の新しい考え方のもう一つの大きな含意は、ライフサイクルで考えるのを止めなければならないということです.
コンポーネントは、いくつかの状態を取り、ビューを返す関数であるため、コンポーネントが呼び出されたとき、どのようにコンポーネントが呼び出されたり更新されたりするかは問題ではありません.同じ入力を与えると、常に同じ出力を返す必要があります.これは、コンポーネントが純粋であることを意味します.
それはフックが唯一の理由の一つですuseEffect の代わりにcomponentDidMount / componentDidUpdate .
あなたの副作用は、常にこのデータフローに従ってください.ユーザーがページを変更するたびにデータベースを更新したいとします.
 function handleChange(newPage) {
    history.push(`/${newPage}`);
    updateDatabase(newPage)
  }
しかし、実際には、ユーザーがクリックするたびにデータベースを更新する必要はありません.
useEffect(()=>{
  updateDatabase(newPage)
})
あなたの見解と同じように、あなたの副作用もあなたの状態の機能であるべきです.

より深く行く
この規則に対する2、3の例外が今反応しています、重要なものはデータ取得です.通常データを取得する方法について考えてみましょう.
const [data, setData] = useState(null)
const [isLoading, setIsLoading] = useState(false)

useEffect(()=>{
 setIsLoading(true)

  fetch(something)
   .then(res => res.json())
   .then(res => {
     setData(res)
     setIsLoading(false)
    })
},[])

return <div>{data ? <DataComponent data={data} /> : 'loading...'}</div>
ここに二重の状態があるisLoading and data ちょうど我々の取得の約束が解決されているかどうかに依存します.
私たちは今このようにしなければなりません.
Svelte 以下のように解決します:
{#await promise}
    <!-- promise is pending -->
    <p>waiting for the promise to resolve...</p>
{:then value}
    <!-- promise was fulfilled -->
    <p>The value is {value}</p>
{:catch error}
    <!-- promise was rejected -->
    <p>Something went wrong: {error.message}</p>
{/await}
反応は何かに似ていますsuspense for data fetching
もう一つの大きなポイントはアニメーションです.現在、60 fpsの状態を更新することはしばしば不可能です.宣言的な方法でそれを解決するすばらしい図書館react spring . Svelte again has a native solution for this そして、それが何か他のものが将来見ている反応であるならば、私は驚かないでしょう.

最後の思考
いつでも
  • あなたのアプリケーションは、しばしば本当の理由のためにreerders
  • 手動で同期しておく必要があります
  • あなたは古い値で問題があります
  • 複雑な論理構造の仕組みを知らない
  • 一歩、あなたのコードを見て、あなたの頭で繰り返してください

    Your view should be expressed as a pure function of your application state.


    読書ありがとう❤
    あなたがその「AHA瞬間」を持っていなかったならば、私はあなたが考えることができて、正確に上記のステップに従うことができるPaginationまたはどんな構成要素も外へ建設することを勧めます.
    あなたが深い話題に潜りたいならば、これらの2つのポストを推薦します:
  • https://medium.com/@mweststrate/pure-rendering-in-the-light-of-time-and-state-4b537d8d40b1
  • https://rauchg.com/2015/pure-ui/
  • あなたが私がより明確にすることができた何かであると思うならば、あるいは、どんな質問/意見も自由にするか、ちょうどここでコメントを残してください.