【実践編】VIII. ユーザーセレクターとプロジェクト編集機能(その2) —— React17+React Hook+TS4 ベストプラクティス、Jira エンタープライズレベルプロジェクトを模倣 (15)


学習コンテンツのソース: React + React Hook + TS ベスト プラクティス - MOOC


元のチュートリアルと比較して、学習の開始時に最新バージョン (2023.03) を使用しました。

アイテム バージョン
反応&反応ダム ^18.2.0
反応ルーターと反応ルーターダム ^6.11.2
^4.24.8
@commitlint/cli および @commitlint/config-conventional ^17.4.4
eslint-config-prettier ^8.6.0
ハスキー ^8.0.3
糸くずステージ ^13.1.2
より美しい 2.8.4
jsonサーバー 0.17.2
クラコレス ^2.0.0
@クラコ/クラコ ^7.1.0
qs ^6.11.0
デイジェス ^1.11.7
反応ヘルメット ^6.1.0
@types/react-helmet ^6.1.6
反応クエリ ^6.1.0
@welldone-software/why-did-you-render ^7.0.1
@emotion/反応 & @emotion/styled ^11.10.6

具体的な構成や動作、内容は異なりますし、「落とし穴」も異なります。


1. プロジェクトの起動: プロジェクトの初期化と構成

2. React と Hook アプリケーション: プロジェクト リストを実装します。

3. TSアプリ:JSゴッドアシスト・強力タイプ

4. JWT、ユーザー認証、非同期リクエスト


5. CSS は実際には非常にシンプルです - CSS-in-JS を使用してスタイルを追加します


6. ユーザー エクスペリエンスの最適化 - 読み込みとエラー状態の処理



7. フック、ルーティング、および URL 状態の管理



8. ユーザーセレクターと項目編集機能

1~3

4. 編集後の更新 - useState の遅延初期化と関数の状態の保存

以前のレガシー問題は現在解決しようとしています

変更src\utils\use-async.ts(新しいrerun方法、最後のrun実行状態を保存):

...
export const useAsync = <D>(...) => {
    
    
  ...
  const [rerun, setRerun] = useState(() => {
    
    })
  ...

  // run 来触发异步请求
  const run = (promise: Promise<D>) => {
    
    
    if (!promise || !promise.then) {
    
    
      throw new Error("请传入 Promise 类型数据");
    }
    setRerun(() => run(promise))
    setState({
    
     ...state, stat: "loading" });
    return promise.then(...).catch(...);
  };

  return {
    
    
    ...
    // rerun 重新运行一遍 run, 使得 state 刷新
    rerun,
    ...state,
  };
};

変数を直接定義する場合と比較して、useState を通じて定義された変数は、State がリセットされない限り、コンポーネントが更新されたときに以前の状態を維持しますが、直接定義すると再初期化されます。

を呼び出してsrc\screens\ProjectList\index.tsx、使用する前に印刷してみてください。

...
export const ProjectList = () => {
    
    
  ...
  const {
    
     isLoading, error, data: list, rerun } = useProjects(useDebounce(param));
  ...
  console.log('rerun', rerun)

  return (
    <Container>
      <h1>项目列表</h1>
      {
    
    /* <Button onClick={rerun}>rerun</Button> */}
      ...
    </Container>
  );
};
...

...エラーが発生しました:Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

rerun前のステップで印刷してみてrun、次を編集してくださいsrc\utils\use-async.ts

...
export const useAsync = <D>(...) => {
    
    
  ...
  const run = (promise: Promise<D>) => {
    
    
    ...
    setRerun(() => {
    
    
      console.log('set rerun')
      run(promise)
    })
    ...
  };
  ...
};

「set rerun」を出力し続けますが、現時点では実行されていないため、値を割り当てるときに直接実行されるのではないかとrerun疑う理由があります。つまり、関数を直接保存することはできません。rerunuseState

codesandbox でテストします。

export default function App() {
    
    
  const [lazyValue, setLazyValue] = React.useState(() => {
    
    
    console.log('i am lazy')
  })
  console.log(lazyValue);
  return (
    <div className="App">
      <button onClick={
    
    () => setLazyValue(() => {
    
     console.log('update lazyValue') })}>
        setCallback
      </button>
      <button onClick={
    
    lazyValue}>call callback</button>
    </div>
  );
}

案の定、割り当て時だけでなく、初期化時にも直接

useState の関数シグネチャを見てください。

/**
 * Returns a stateful value, and a function to update it.
 *
 * @version 16.8.0
 * @see https://reactjs.org/docs/hooks-reference.html#usestate
 */
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

initialStateはジョイントタイプであり、S一般的に使用される形式であることがわかりますが、() => Sなぜ個別にリストする必要があるのでしょうか?

公式ドキュメントを参照できます:遅延初期状態 | フック API インデックス – React

ドキュメントから、この初期化メソッドは 1 回だけ実行され、初期化値を取得するために複雑な計算が必要な場合に使用されることがわかります (高価な計算、パフォーマンスの消費)。

それなら、外部に別の機能を追加してみてはいかがでしょうか。それを試してみてください:

export default function App() {
    
    
  const [callback, setCallback] = React.useState(() => () => {
    
    
    console.log('i am callback')
  })
  console.log(callback);
  return (
    <div className="App">
      <button onClick={
    
    () => setCallback(() => () => {
    
     console.log('update callback') })}>
        setCallback
      </button>
      <button onClick={
    
    callback}>call callback</button>
    </div>
  );
}

当然のことながら、これは初期化の直後に呼び出すことができcallbacksetCallback初期化の後に呼び出す別の関数です。

この方法に加えて、次の方法も使用できますuseRef

export default function App() {
    
    
  const callbackRef = React.useRef(() => console.log('i am callback'));
  const callback = callbackRef.current;
  console.log(callback);
  return (
    <div className="App">
      <button onClick={
    
    () => (callbackRef.current = () => console.log('update callback'))}>
        setCallback
      </button>
      <button onClick={
    
    callback}>call callback</button>
    </div>
  );
}

https://codesandbox.io/s/blissful-water-230u4?file=/src/App.js

を使用する場合useRef、定義された値を変更してもコンポーネントの再レンダリングはトリガーされないため、callback以前の値のままであり、直接実行する必要があることに注意してください。callbackRef.current()

export default function App() {
    
    
  const callbackRef = React.useRef(() => console.log("i am callback"));
  const callback = callbackRef.current;
  console.log(callback);
  return (
    <div className="App">
      <button
        onClick={
    
    () =>
          (callbackRef.current = () => console.log("update callback"))
        }
      >
        setCallback
      </button>
      <button onClick={
    
    () => callbackRef.current()}>call callback</button>
    </div>
  );
}

次に、最初の方法を使用して、それに対処するための追加の関数レイヤーを追加します。

5. 編集後のリフレッシュ機能

編集src\utils\use-async.ts(外部に関数の追加レイヤーを追加):

...
export const useAsync = <D>(...) => {
    
    
  ...
  const [rerun, setRerun] = useState(() => () => {
    
    })
  ...

  const run = (promise: Promise<D>) => {
    
    
    ...
    setRerun(() => () => run(promise))
    ...
  };
  ...
};

か否か。分析を通じてrerunrunこの実行はまだ最後の実行Promise(インターフェイスへの最後の呼び出し) であることがわかり、そのため、最後の実行から取得されたデータはPromise当然最後のデータであり、更新する必要があることを示していますPromise(インターフェイスを思い出してください)。

編集src\screens\ProjectList\index.tsx(rerunボタンのコメントを解除):

...
export const ProjectList = () => {
    
    
  ...
  return (
    <Container>
      <h1>项目列表</h1>
      <Button onClick={
    
    rerun}>rerun</Button>
      ...
    </Container>
  );
};
...

編集src\utils\project.ts(個別に抽出されfetchProject、最初の実行がrun最初のパラメータとして使用され、事前に実行されたパッケージ化が 2 番目のパラメータとして使用されます):

...
export const useProjects = (param?: Partial<Project>) => {
    
    
  ...
  const fetchProject = () => client("projects", {
    
     data: cleanObject(param || {
    
    }) })

  useEffect(() => {
    
    
    run(fetchProject(), {
    
     rerun: fetchProject });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [param]);

  return result;
};
...

編集src\utils\use-async.ts(実行用のrunConfigを追加):

...
export const useAsync = <D>(...) => {
    
    
  ...
  // run 来触发异步请求
  const run = (promise: Promise<D>, runConfig?: {
    
     rerun: () => Promise<D> }) => {
    
    
    ...
    setRerun(() => () => {
    
    
      if(runConfig?.rerun) {
    
    
        run(runConfig.rerun(), runConfig)
      }
    });
    ...
  };
  ...
};

定義済みrunConfigはオプションのパラメータですが、次回利用可能にしたい場合はrerun、前回の事前リクエストを設定する必要があるため、に追加する必要setRerunrunConfigあります。また、他の場所でこの機能が必要ない場合は、追加する必要はありません。追加する必要はありません!

ページを表示し、ボタンをクリックして実行するとrerun、機能します。

次に調整して編集後に自動化しますrerun

変更src\screens\ProjectList\index.tsx(前のテストで使用したボタンとログの印刷を削除し、:Listを渡します):refreshrerun

...
export const ProjectList = () => {
    
    
  ...
  return (
    <Container>
      ...
      <List refresh={
    
    rerun} loading={
    
    isLoading} users={
    
    users || []} dataSource={
    
    list || []} />
    </Container>
  );
};
...

変更src\screens\ProjectList\components\List.tsx(着信 incoming を受け取りrefreshstarProject最後に実行します):

...
interface ListProps extends TableProps<Project> {
    
    
  users: User[];
  refresh?: () => void;
}

// type PropsType = Omit<ListProps, 'users'>
export const List = ({
     
      users, ...props }: ListProps) => {
    
    
  const {
    
     mutate } = useEditProject();
  // 函数式编程 柯里化
  const starProject = (id: number) => (star: boolean) => mutate({
    
     id, star }).then(props.refresh);
  return (...);
};

ページ効果を確認してください。完璧です。

残りの質問は次のとおりです。

  • 楽観的なアップデート
    • 成功しましたか? 読み込みがありません: ロールバックとプロンプト
  • 呼び出したいメソッドがトリガーコンポーネントから遠すぎる場合はどうすればよいですか?
    • ステータス向上?複雑すぎると役に立たない
    • グローバルな状態管理

一部の参考ノートはまだ草案段階にあるため、今後の内容に注目してください。

おすすめ

転載: blog.csdn.net/qq_32682301/article/details/131931899