React で共有するフック

1.フックとは

コンピューター プログラミングでは、「フッキング」という用語は、ソフトウェア コンポーネント間で渡される関数呼び出し、メッセージ、またはイベントをインターセプトすることによって、オペレーティング システム、アプリケーション、またはその他のソフトウェア コンポーネントの動作を変更または強化するために使用されるさまざまなテクニックを指します。これらのインターセプトされた関数呼び出し、イベント、またはメッセージを処理するコードは、「フック」と呼ばれます。
ここに画像の説明を挿入
Reactにはクラス(class)コンポーネントとファンクション(関数)コンポーネントの2種類のコンポーネントがあります。

クラスはデータとロジックをカプセル化したものです。つまり、コンポーネントの状態メソッドとアクション メソッドが一緒にカプセル化されます。クラス記述方法を選択した場合は、関連するデータと操作を同じクラスに記述する必要があります。

一般に、関数は値を返すという 1 つのことだけを行う必要があります。複数の操作がある場合は、各操作を別の関数として記述する必要があります。また、データの状態はアクション メソッドから切り離される必要があります。この哲学によれば、React の機能コンポーネントは 1 つのことだけを行う必要があります。それは、コンポーネントの HTML コードを返すことであり、他には何も行わないことです。このように、単純なデータの計算(変換)だけを行う関数を、関数型プログラミングでは「純粋関数」と呼びます。

**関数型プログラミングでは、データ計算とは関係のない操作を「副作用」と呼びます。**関数に副作用を引き起こす操作が直接含まれている場合、その関数は純粋関数ではなくなり、不純関数と呼ばれます。純粋な関数は、間接的な手段 (つまり、他の関数呼び出し) を通じてのみ副作用を含むことができます。

Hook は React 関数コンポーネントの副作用ソリューションであり、関数コンポーネントに副作用を導入するために使用されます。関数コンポーネントの本体は、コンポーネントの HTML コードを返すためにのみ使用する必要があり、他のすべての操作 (副作用) はフックを介して導入する必要があります。

フックを使用すると、クラス コンポーネントを作成せずに、React が状態 (state) やその他の React 機能を使用できるようになります。

2. フックがある理由

  • コンポーネント間でステートフル ロジックを再利用するのは難しい
    React には、ステートフル ロジックを再利用するネイティブな方法が提供されていません。通常、クラス コンポーネントのロジックの再利用には HOC (高次コンポーネント) またはレンダリング プロパティが使用されますが、そのようなソリューションでは通常、コンポーネント構造を再編成する必要があり、入れ子になった抽象化層コンポーネントが多すぎると簡単に「入れ子地獄」が形成される可能性があります。
    フックを使用すると、コンポーネントからステートフル ロジックを抽出できるため、コンポーネントを個別にテストして再利用できます。フックを使用すると、コンポーネント階層を変更せずにステートフル ロジックを再利用できます。
// 例如在对于接口请求的情况,每个页面都需要在componentDidMount中调用接口,调用接口时需要将state中的loading置为true,结束后,再置为false。
class Test extends PureComponent {
    
    
    state = {
    
    
        loading: false,
        data: null
    }
    componentDidMount() {
    
    
        this.setState({
    
    
            loading: true,
       })
        fakeGet("xxx.com/xxx").then(res => {
    
    
            this.setState({
    
    
                data: res,
                loading: false
            })
        })
    }
    render() {
    
    
        const {
    
     loading, data } = this.state;
        return (
            <div>
                {
    
    
                   loading ? <Loading /> : (
                        data.map(item => (<Item data={
    
    item} />))
                    )
                }
            </div>
        )
    }
}

// 因为类组件的state是自身特有的,所以不能直接复用,因此每个类组件都需要写一遍这个逻辑

// 如果使用hooks呢
const Test = ({
     
     }) => {
    
    
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(null);    
    useEffect(() => {
    
    
        setLoading(true);
        fakeGet("xxx.com/xxx").then(res => {
    
    
            setData(res);
            setLoading(false)
        })
    })
    return (
        <div>
            {
    
    
                loading ? <Loading /> : (
                    data.map(item => (<Item data={
    
    item} />))
                )
            }
        </div>
    )
}

// 这时可以把状态提取至公共状态
const useRequest = (option) => {
    
    
    const {
    
     url, ...opt } = option;
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(null);
    useEffect(() => {
    
    
        setLoading(true);
        fakeGet(url, opt).then(res => {
    
    
            setData(res);
            setLoading(false)
        })
    })
    return {
    
     loading, data };
}
const Test = ({
     
     }) => {
    
    
    const {
    
     loading, data } = useRequest({
    
    
        url: "xxxx.com/xxx",
        method: "GET",
    })
    return (
        <div>
            {
    
    
                loading ? <Loading /> : (
                    data.map(item => (<Item data={
    
    item} />))
                )
            }
        </div>
    )
}
// 之后需要做接口请求的地方都可以使用useRequest这个hooks,不用重复定义loading等状态。
  • 複雑なコンポーネントの理解が困難になる
    最初は単純だったコンポーネントを保守しなければならないことがよくありますが、その後、ステートフル ロジックと副作用の管理不能な山になってしまいます。通常、各ライフサイクル メソッドには、無関係なロジックのセットが含まれています。たとえば、
    コンポーネントはcomponentDidMountおよびcomponentDidUpdateで何らかのデータフェッチを実行する場合があります。
    同じComponentDidMount メソッドには、イベント リスナーを設定し、componentWillUnmount でクリーンアップを実行する、無関係なロジックが含まれる場合もあります。
    相互に関連して変更されるコードは分離されますが、まったく関係のないコードは 1 つのメソッドに結合されます。これにより、エラーや不一致が簡単に発生する可能性があります。
    フックは、ライフサイクル メソッドに基づいて強制的に分割するのではなく、関連するコンテンツ (サブスクリプションの設定やデータの取得など) に基づいてコンポーネントをより小さな関数に分割できます。useReducer を使用してコンポーネントのローカル状態を管理し、予測可能性を高めるオプションもあります。
    フックはほとんどのライフ サイクルをシミュレートできますが、getSnapshotBeforeUpdate、getDerivedStateFromError、componentDidCatch などのライフ サイクル API をフックを使用して完全に置き換えることはできません。

3. カプセル化のためのフック、HOC、レンダー Props の違い

1. HOC - 高次コンポーネント

一般的な HOC の使用方法は次のとおりです。connect を使用してストアに接続し、withRouter を使用してルーティング パラメータを取得します。このネスト方法は非常に読みやすく保守しやすいですが (不快なのは 2 レベルのネストだけです)、compose を使用して上位コンポーネントまたはデコレータを組み合わせることができます。しかし、本質は HOC の入れ子です。

const App = withRouter(connect(commentSelector)(WrappedComponent))// 优化 可以使用一个 compose 函数组合HOC 
const enhance = compose(withRouter, connect(commentSelector))const App = enhance(WrappedComponent)// 优化 使用装饰器  
@connect  
class App extends React.Component {
    
    }

HOC 呼び出しごとにコンポーネント インスタンスが生成されます。多層ネストにより React 仮想 Dom の深さが増し、パフォーマンスに影響します。さらに、あまりにも多くの層をラップすると、props 属性が上書きされる可能性があります。さらに、HOC はユーザーにとってブラック ボックスに近いため、使用するには具体的な実装を確認する必要があります。

2. 小道具をレンダリングする

以下は、ウィンドウ サイズの変更を多重化し監視するロジックです。

<WindowSize> 

(size) => <OurComponent size={
    
    size} /> 

</WindowSize>

然后,如果再想复用监听鼠标位置的逻辑
<WindowSize> 
(size) => ( 
    <Mouse> 
    (position) => <OurComponent size={
    
    size} mouse={
    
    position} /> 
 </Mouse> ) 
</WindowSize>

この時点では、もう他のロジックを再利用したくないかもしれません。レンダー プロパティを使用すると、ユーザーのブラック ボックス、属性名のオーバーライドなど、hoc に存在するいくつかの問題が解決されますが、再利用ロジックが多すぎる場合は、ネストが深すぎるため、コールバック地獄が形成されます。

3. フック - 状態ロジックを再利用するネイティブな方法を提供します。

// 复用监听 window size 变化的逻辑 const size = useSize() 

// 复用监听鼠标位置的逻辑 const position = useMouse()

カスタムフックで書き換えた後、HOCを書いてpropsを返したいのは「甘い」ではありませんか。状態ロジックを再利用するためにフックをカスタマイズする方法は、React によってネイティブにサポートされており、React コンポーネントとは異なり、フックのカスタマイズは使用から始まる機能であるため、テストと再利用が容易です。また、「Zhenxiang」のカスタムフックでは他のフックも使用可能です。

4. 基本的なフック

useState (ステートフック)

InitialValue は関数を渡して、初期値を返すことができます。
setState は、次のようなデータを自動的にマージしません。

const [data, setState] = useState({
    
    a:1, b:2})
   setState({
    
     c: 1 });
// state会被改成{c:1},而不是{a:1, b:2, c:1}

setState は、前後の状態が同じかどうかを判断するために Object.is を使用します。同じである場合、
複数の setState または異なる useStates の setState メソッドをトリガーしません。React の「制御可能な」プロセス内にある場合 (たとえば、同期イベント コールバック、useEffect 同期関数など) が最適化され、1 つのレンダリングのみがトリガーされます

const Demo4 = () => {
    
    
  const [number, setNumber] = useState(0);
  // 第一次为0
  // effect后为 1
  // click后为 4,说明多个setState进行了合并,而回调函数的setState将正常改变
  // 再增加一个setTimeout会怎么样?
  console.log(0, number);
  useEffect(() => {
    
    
    setNumber(number + 1);
    console.log('1', number);    // 0
  }, [])

  function handleAdd() {
    
    
    setNumber(number + 1);
    console.log(2, number);    // 1
    setNumber(number + 1);
    console.log(3, number);    // 1
    setNumber(number + 1);
    console.log(4, number);    // 1
    setNumber((prev) => {
    
    
      console.log('prev', prev);    // 2
      return prev + 1;
    })
    setNumber((prev) => {
    
    
      console.log('prev1', prev);    // 3
      return prev + 1;
    })

    // 如果增加下面这个,会发生什么呢?
    // setTimeout(() => {
    
    
    //   setNumber(number + 1);
    //   console.log(6, number);      // ??
    // }, 0)
    console.log(5, number);    // 1
  }
  return (
    <div>
      <p>number: {
    
    number}</p>
      <button onClick={
    
    handleAdd}>+++</button>
    </div>
  )
}

粒度論理モジュールの分割に応じて、複数の状態が関連する場合は、まとめてパッケージ化することを推奨します (例: ページネーション状態の current、total、pageSize などの状態は、パフォーマンスの最適化を考慮して
分割する必要があります)。
無意味なレンダリングを避けてください。たとえば、ロード中、リクエスト状態、データソース、エラー、その他の状態で
もコードの保守性を考慮する必要があります。クラス コンポーネントのようにすべての状態を一緒に詰め込まないでください。例:テーブル状態にページジャンション、クエリ、選択などの多くの状態があり、
それらを状態にカプセル化したい場合は、redux のストア メソッドとディスパッチ メソッドを使用して状態をより適切に管理するために useReducer の使用を検討してください

// 类组件下

addHandleTimeout2 = () => {
    
    
    const {
    
     count } = this.state;
    console.log(`----timeout count ---- ${
      
      count}`) // 0
    this.setState({
    
    count:count + 1});
    setTimeout(() => {
    
    
        console.log(`----this.state.count---- ${
      
      this.state.count}`); // 1
        console.log(`----count---- ${
      
      count}`); // 0
    }, 2000);
}

// hook function component
const addHandleTimeout2 = () => {
    
    
    console.log(`----timeout count ---- ${
      
      count}`) // 0
    setCount(count + 1);
    setTimeout(() => {
    
    
        console.log(`----count---- ${
      
      count}`); // 0
    }, 2000);
}
// 会输出什么?count初始值为0。

最初はクラスコンポーネントの説明です。state
は Immutable で、setState の後に新しい state 参照が生成されます。
ただし、クラス コンポーネントは this.state を通じて状態を読み取るため、コードが実行されるたびに最新の状態参照が取得されるため、4 回のクイック クリックの結果は 4 4 4 4 になります。
次に、関数コンポーネント useState の説明があります:
useState によって生成されたデータも不変です。配列の 2 番目のパラメーターを通じて新しい値が設定された後、元の値は次のレンダリングで新しい参照を形成します。
ただし、これでは状態は読み込まれないので、setTimeoutごとにその時点のレンダリングクロージャ環境のデータを読み込み、最新のレンダリングでは最新の値が変わりますが、古いレンダリングでは状態は古い値のままです。

2. useReducer(アクションフック)

React 自体は状態管理機能を提供しておらず、通常は外部ライブラリを使用する必要があります。このために最も一般的に使用されるライブラリは Redux です。
Redux の中心的な概念は、コンポーネントがステート マネージャーと通信するためにアクションを発行するということです。状態マネージャーはアクションを受け取った後、Reducer 関数を使用して新しい状態を計算します。Reducer 関数の形式は (state, action) => newState です。
useState の代替手段。また、タイプ (state, action) => newState のレデューサーを受け入れ、ディスパッチ メソッドとペアになった現在の状態を返します。
公式の推奨事項は、状態を複数の状態変数に分割し、各変数に含まれる異なる値が同時に変化することです。
状態によっては、状態ロジックがより複雑になるなど、一部のシナリオでは useState より useReducer の方が適しています。複数のサブ値、または次の状態が含まれています。状態は前の状態などに依存します。また、useReducer を使用すると、詳細な更新をトリガーするコンポーネントのパフォーマンスを最適化することもできます (useState はマージせずに値の更新を直接置き換えるため、深いレベルの更新操作に遭遇すると、useReducer の力がなければ、さらに面倒になります)。
さらに、もう 1 つの利点があります。Reducer は実際には UI とは何の関係もない純粋な関数です。useReducer スキームを使用すると、自動化されたテスト ケースの構築が容易になります。

// 使用方式如下

const initialState = {
    
    count: 0};
function reducer(state, action) {
    
    
  switch (action.type) {
    
    
    case 'increment':
      return {
    
    count: state.count + 1};
    case 'decrement':
      return {
    
    count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
    
    
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {
    
    state.count}
      <button onClick={
    
    () => dispatch({
    
    type: 'decrement'})}>-</button>
      <button onClick={
    
    () => dispatch({
    
    type: 'increment'})}>+</button>
    </>
  );
}

3. useEffect (サイドエフェクトフック)

useEffect を使用すると、componentDidMount、componentDidUpdate、componentWillUnmount など、多くのクラス コンポーネントのライフ サイクルをシミュレートできます。componentDidMount
やcomponentDidUpdate とは異なり、useEffect に渡される関数は、ブラウザーがレイアウトと描画を完了した後に遅延イベントで呼び出されます。これにより、操作の大部分がブラウザの画面更新をブロックしないため、サブスクリプションの設定やイベント処理などの多くの一般的な副作用シナリオに適しています。
使用方法最初のパラメータは副作用関数です。副作用関数は
、次回副作用が実行される
とき、またはコンポーネントがキャンセルされたときに呼び出される関数を返すように選択できます
。依存関係配列が渡されない場合、コンポーネントはレンダリングされるたびに実行され、空の配列が渡された場合は、コンポーネントの作成時に 1 回だけ実行されます。
副作用関数は、いかなる場合でも少なくとも 1 回呼び出す必要があります。

const Demo5 = () => {
    
    
  const [name, setName] = useState('');
  useEffect(() => {
    
    
    console.log('name:', name)
  }, [name])
  return (
    <div>
      <p>name: {
    
    name}</p>
      <button onClick={
    
    () => setName("aaa")}>change</button>
    </div>
  )
}

クロージャの問題: 各レンダリング実行のエフェクトは現在のレンダリングの最新の変数を取得しますが、クリーンアップは最後のレンダリングの古い変数を取得します。 useEffect を使用して
副作用操作を完了します。useEffect に割り当てられた関数は、コンポーネントが画面にレンダリングされた後に実行されます。エフェクトは、React の純粋に関数的な世界から命令型の世界への逃げ道と考えることができます。
実際の運用ではuseEffectを使って状態を監視し、監視している状態が変化したときにメソッドが実行されます。
useEffect と同様に、useLayoutEffect もあります。これは、すべての DOM 変更後にエフェクトを同期的に呼び出します。これを使用して、DOM レイアウトを読み取り、再レンダリングを同期的にトリガーできます。useLayoutEffect 内の更新プランは、ブラウザーが描画を実行する前に同期的に更新されます。これにより、ブラウザの描画もブロックされます。
useEffect の操作で DOM を処理し、ページのスタイルを変更する必要がある場合は、これを使用する必要があります。そうしないと、スプラッシュ スクリーンの問題が発生する可能性があります (たとえば、状態に応じて幅、高さ、または位置を計算する場合) 、useLayoutEffect を使用する必要があり、残りの 90% 以上のシーンは useEffect を使用するだけで済みます)

4. コールバック/メモに入ります

安定した変数を確保し、パフォーマンスを最適化して、無意味なレンダリングを回避します。deps
配列は埋める必要があります。埋められていない場合は意味がありません。
ほとんどの場合、state、props、および派生変数が使用されている限り、それらを含める必要がありますdeps 配列に格納する, それ以外の場合は、 It is always the value ofInitial state
. 以下の条件が満たされる場合、メモは必要ありません:
値が他のフックに依存しない、
値が props として他のコンポーネントに渡されない
、値は単純な型であり、計算は基本的に無料です

const memoizedCallback = useCallback(
  () => {
    
    
    doSomething(a, b);
  },[a, b]);

// 只要a b不发生改变,这个值也不会发生改变,computeExpensiveValue就只会执行一次
// 主要是用来缓存计算量比较大的函数结果,可以避免不必要的重复计算
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
return (
    <div onClick={
    
    memoizedCallback}>
        {
    
    memoizedValue}
    </div>
)

5. useRef

状態とは異なり、変数を保存すると、値の変更はレンダリングをトリガーしません
。値が変更されてもレンダリングはトリガーされないため、レンダリングをトリガーしたくない変数を保存するために使用されます。

function TextInputWithFocusButton() {
    
    
  const inputEl = useRef(null);
  const onButtonClick = () => {
    
    
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={
    
    inputEl} type="text" />
      <button onClick={
    
    onButtonClick}>Focus the input</button>
    </>
  );
  // 也可以直接给ref.current赋值,如下方的例子usePreviousValue,通过ref来保存之前的值
}

// ref的穿透操作,父级使用子组件中的方法
const Parent = () => {
    
    
  const childRef = useRef(null);
  const handleClick = () => {
    
    
    if (childRef) {
    
    
      childRef.current.fff();
    }
  };

  return (
    <div>
      <Child ref={
    
    childRef} />
      <button onClick={
    
    handleClick}>click</button>
    </div>
  );
};

// 子组件中需要使用forwardRef包裹一下,在props中是获取不到ref的值,refs 不会透传下去。
// 这是因为 ref 不是 prop 属性。就像 key 一样,其被 React 进行了特殊处理
// 否则你就需要改变一下ref的名字,如aref等,避开关键字,就可以在props中拿到了

const Child = forwardRef((props, ref) => {
    
    
  const currentRef = useRef(null);
  const [number, setNumber] = useState(0);
  useImperativeHandle(ref, () => ({
    
    
    fff() {
    
    
      currentRef.current.focus();
      setNumber(number + 1);
    }
  }));
  return (
    <>
      <p>number: {
    
    number}</p>
      <input ref={
    
    currentRef} />
    </>
  );
});

6. useContext (共有状態フック)

コンテキストに渡すと、その値を直接取得できます

const themes = {
    
    
  light: {
    
    
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    
    
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);
function App() {
    
    
  return (
    <ThemeContext.Provider value={
    
    themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
    
    
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
    
    
  const theme = useContext(ThemeContext);
  return (
      <button
          style={
    
    {
    
    
              background: theme.background,
              color: theme.foreground
          }}
      >
          I am styled by theme context!
      </button>
  );
}

7. メモ (コンポーネントを思い出深いものにします)

クラスコンポーネントの shouldComponentUpdate と同様に、prevProps と nextProps の比較に応じて内部コンポーネントを更新する必要があるかどうかを判断するために使用されます。
shouldComponentUpdate とは異なり、 shouldComponentUpdate は true を返した場合にのみ更新されますが、memo は更新がないことを示す true を返します。
可能な限りすべてのコンポーネントの外側にメモメソッドを追加します。

const Test = () => <div>test</div>
export default memo(Test, (prevProps, nextProps) => {
    
    
    if (prevProps.xxx === nextProps.xxx) {
    
    
       return true;
    }
    return false;
})

5. フックの仕様

1. 最上位レベルでのみフックを使用する

ループ、条件分岐、またはネストされた関数内でフックを呼び出さないでください。フックは常に React 関数の最上位レベルで、リターンの前に呼び出すようにしてください。このルールに従うことで、レンダリングのたびにフックが同じ順序で呼び出されるようにすることができます。簡易版の実装原理については後述する。

2. React 関数でのみフックを呼び出す

通常の JavaScript 関数でフックを呼び出さないでください。あなたはできる:

  • Reactの関数コンポーネントでフックを呼び出す
  • カスタムフックで他のフックを呼び出す

3. カスタムフックは「use」で始まる必要があります

カスタム フックは、状態ロジック (サブスクライブの設定や現在の値の保存など) を再利用するためのメカニズムであり、カスタム フックが使用されるたびに、その中のすべての状態と副作用が完全に分離されます。
フックを実装し、その原理を理解する

// 实例代码
function App() {
    
    
  // index = 0;
  const [count, setCount] = useState(0);
  // const [count2, setCount2] = useState(0);
  return (
    <div>
      <div>{
    
    count}</div>
      <Button
        onClick={
    
    () => {
    
    
          setCount(count + 1);
        }}
      >
        点击
      </Button>
      // <div>{count2}</div>
      // <Button
      //   onClick={() => {
    
    
      //     setCount2(count2 + 1);
     //    }}
      // >
      //   点击
      // </Button>
    </div>
  );
}

// 先来一个useState,但是setState后数据并没有更新,原因是每次都被初始化了,所以要将值记录在外部,优先读取外部数据,没有的话,在使用初始化数据,这样就保证了数据的持久性。
let value;
function useState(initialValue) {
    
    
  var state = initialValue;
  value = value || initialValue;
  function setState(newState) {
    
    
    value = newState;
    render();
  }
  return [value, setState];
}

// 第二步,这个useState只能写一个,写第二个useState的时候,就会覆盖掉前面的,所以再改一下
let memoizedState = [];
let cursor = 0;
function useState(initialValue) {
    
    
  const currentIndex = cursor;
  cursor++;
  memoizedState[currentIndex] = memoizedState[currentIndex] || initialValue;
  function setState(newState) {
    
    
    memoizedState[currentIndex] = newState;
    render(); // 模拟 reRender,这一行不需要关心
  }
  return [memoizedState[currentIndex], setState];
}

useEffect を実装しましょう。 useEffect にはいくつかの特性があることがわかっています。

callback と dependency array の 2 つのパラメータがあります。依存関係が存在しない場合、コールバックはレンダリングが実行されるたびに実行されます。依存関係が存在する場合、コールバックは変更されたときのみ実行され、変更されたときに実行されます

初期化されます。

function useEffect(callback, depArray) {
    
    
  const currentIndex = cursor;
  cursor ++;
  // 从数组中取出上次保存的值,用于此次判断
  const {
    
     callback: unmountCallback , depArray: oldDepArray } = memoizedState[currentIndex] || {
    
    };
  unmountCallback && unmountCallback();
  // 没有依赖项,或者依赖项中有一个发生改变,都需要触发callback
  const noDep = !depArray;
  const dspHaveChange = oldDepArray ? !!depArray && depArray.some((item, index) => item !== oldDepArray[index]) : true;
  const newEffect = {
    
    };
  newEffect.depArray = depArray;
  if(noDep || dspHaveChange) {
    
    
    newEffect.callback = callback();
  }
  memoizedState[currentIndex] = newEffect;
}

この時点で、次の質問に答えることができるはずです。

Q: 2 番目のパラメータが空の配列 (componentDidMount と同等) なのはなぜですか?
A: 依存関係は変更されていないため、コールバックが 2 回実行されることはありません。

React では、配列は単一リンク リストに似た形式に置き換えられます。すべてのフックを次まで順番に連結します。

6. カスタムフック

1. useDidMount

// 利用useEffect的特性
function useDidMount(fn) {
    
    
    useEffect(() => {
    
    
        fn()
    }, [])
}

2. useWillUnmount

// 利用useEffect的第一个参数的返回值会在每次渲染前执行的特性,模拟卸载组件。
// useRef则可以持久保存数据,且不触发render
function useWillUnmount(fn) {
    
    
    const fnRef = useRef(null);
    fnRef.current = fn;
    useEffect(() => {
    
    
        return () => {
    
    
            fnRef.current();
        } 
    }, [])
}

3.ForceUpdateを使用する

// 用于刷新本组件
function useForceUpdate() {
    
    
    const [, setState] = useState(false);
    const forceUpdate = useCallback(() => {
    
    
        setState((v) => !v);
    })
    return forceUpdate;
}

// antd 版
export default function useForceUpdate() {
    
    
  const [, forceUpdate] = useReducer(x => x + 1, 0);
  return forceUpdate;
}

4.前の値を使用する

// 获取上一次render时某个变量的值
function usePreviousValue(value) {
    
    
    const currentRef = useRef(null);
    const prevRef = useRef(null);
    // 1、直接改变
    prevRef.current = currentRef.current;
    currentRef.current = value;
    // 2、获取与之前不一样的值
    const shouldUpdate = !Object.is(current.value, value);
    if (shouldUpdate) {
    
    
        prevRef.current = currentRef.current;
        currentRef.current = value;
    }
    return prevRef.current;
}

5. ブール値を使用する

// 可以用于切换modal的visible属性
function useBoolean(initValue) {
    
    
    const [state, setState] = useState(initValue || false);
    const actions = useMemo(() => {
    
    
        return {
    
    
            setTrue() {
    
    
                setState(true);
            },
            setFalse() {
    
    
                setState(false);
            },
            toggle() {
    
    
                setState((v) => !v);
            },
            setValue(value) {
    
    
                setState(value);
            },
        }
    }, [])
    return [state, actions]
}

// 这个方法也可以使用useReducer改造
const reducer = (state, action) => {
    
    
    switch(action.type) {
    
    
        case 'true':
            return true;
        case 'false':
            return false;
        case 'toggle':
            return !state;
        case 'set':
            return action.type;
    } 
}
const [state, dispatch] = useReducer(reducer, false);

6. useRequest

function useRequest(id) {
    
    
    const [loading, setLoading] = useState(false);
    const [body, setBody] = useState(null);
    const count = useRef(0);
    useEffect(() => {
    
    
        const currentCount = count.current;
        setLoading(true);
        getData(id).then(res => {
    
    
            if (currentCount !== count.current) return;
            setLoading(false);
            setBody(res);
        })
        return () => {
    
    
            count.current ++;
        }
    }, [id])
    return [loading, body];
}

const [loading, body] = useRequest(id);

7.UpdateEffect を使用する

// 监听依赖完成渲染后的操作,return的操作是在下次执行该副作用之前调用
const useUpdateEffect = (effect, deps) => {
    
    
  const isMounted = useRef(false);
  useEffect(() => {
    
    
    // 第一次执行是在mount阶段,故不返回effect,第二次执行之后才会设置effect,在第三次执行前,会执行effect方法
    if (!isMounted.current) {
    
    
      isMounted.current = true;
    } else {
    
    
      return effect();
    }
  }, deps);
};

7. 推奨フックライブラリ

  • ahooks、利用可能なフックが多数あり、useRequest 実装、ロード状態、ページングに基づいて、useAntdTable などの antd に関連付けられたフックがいくつかあります。
  • hox: フック内の状態マネージャー。コードは単純なので、興味があれば見てください。
// 定义modal
import {
    
     createModel } from 'hox';
/* 任意一个 custom Hook */
function useCounter() {
    
    
  const [count, setCount] = useState(0);
  const decrement = () => setCount(count - 1);
  const increment = () => setCount(count + 1);
  return {
    
    
     count,
    decrement,
    increment
  };
}
export default createModel(useCounter)
// 使用modal
import useCounterModel from "../models/useCounterModel";
function App(props) {
    
    
  const counter = useCounterModel();
  return (
    <div>
      <p>{
    
    counter.count}</p>
      <button onClick={
    
    counter.increment}>Increment</button>
    </div>
  );
}

8. コードをリファクタリングする方法

1. リファクタリングの目標

リファクタリングの主な目的は、既存のコードの設計を改善することであり、欠陥の修正や新しい機能の追加などではありません。

リファクタリングには、変数名の変更やディレクトリの再配置などの単純な物理リファクタリング、またはサブ関数の抽出や冗長設計の合理化などの少し複雑な論理リファクタリングがあります。ただし、それらはいずれも既存のコードの機能を変更しません。

リファクタリングを行うと、スパゲッティ状の乱雑なコードをパイ生地のようなきれいなコードに変えることができます。クリーンなコードはより堅牢であるため、強固なテスト ネットを構築するのが簡単です。同時に、初心者や上級者でも安心して修正できます。

リファクタリング後は、コードのロジックが一目で明確になり、拡張や変更が非常に便利になり、障害が発生したときにすぐに特定して修復できることが期待されます。先人たちが苦労したところでは、未来の世代が倒れることはなくなり、未来の世代は先人の思考の結果をそのまま借りることができる。つまり、高度に人間化され、マンパワーと頭脳を大幅に解放します。

2. 一目でわかるコードとは?

組み立てられたロジックコードに出会うたびに頭を抱えることになりますが、後になって、これらのコードも同じ間違いを犯していたことがわかります。何がステップであり、何が実装の詳細なのかは明確ではありません。ステップと詳細を一緒に記述すると、特に何年にもわたって反復された種類のコードがどこにでもある場合に、災害が発生します。「Hooks はコード分割のための効率的なツールですが、非常に柔軟でもあります。業界にはこれほど一般的なコーディング標準はありませんでしたが、私は別の観点を持っています。Redux のようなモジュール型コーディング標準は必要ないと思います。」関数型プログラミングであるため、関数型プログラミングの一般原則に従います。関数型プログラミングで最も重要なことは、ステップと実装の詳細を分割することです。そのようなコードは読みやすく、読みやすいコードは責任のあるコードです。

ステップと詳細を区別するにはどうすればよいですか? 非常に簡単な方法で、要件を整理するときにフローチャートを使って要件を表現しますが、このとき具体的な実装を伴わないため、各ノードは基本的にステップとなります。説明が多すぎて、少し長くなってしまったので、理解していただけると思います。ステップと詳細が明確になると、各ステップが関数であり、クラス内にこのようなグローバル変数が存在しないため、リファクタリングにも非常に役立ちます。他のステップ関数。同様に、機能化後は単体テストが非常に簡単になることは間違いありません。

3. エンコード値ETC

ETC のコーディング値は、単一責任原則、デカップリング原則など、多くの優れたコーディング原則の本質であり、それらはすべて ETC の値を具体化しています。ユーザーに適応できるデザインが良いデザインですが、コードも変化を受け入れ、変化に適応する必要があります。したがって、ETCを信じる必要があります。価値観は、コードを書くときに意思決定をするのに役立ちます。何をすべきか教えてくれますか? それともそうしますか?彼は、さまざまなコーディング方法の選択を支援し、コーディングする際にはそれが潜在意識になるはずです。この価値観を受け入れる場合は、コーディングするときは常にこの価値観に従うことを思い出してください。

要約:

  1. 各関数で処理するものをできるだけシンプルにし、保守とテストが容易なできるだけ純粋な関数を作成します。これも分かりやすいです。
  2. リファクタリングは行われていません。最初にテストしてください
    。 リファクタリングの前に、機能の欠落やエラー修正の機能がないことを確認するための十分なテスト ケースが必要です。
  3. 機能ポイントを整理し、最初に問題点を見つける
    • たとえば、繰り返し記述される必要がある多くのコードをメソッドに抽出できます。
    • たとえば、フォームの送信は配信システムの多くのシナリオで使用されるため、記述を簡素化する方法を検討できます (配列を使用して各 formItem をレンダリングしますか? 配列の構造をどのように定義する必要がありますか?)。
    • コードの再利用性とスケーラビリティを向上させる方法
    • リファクタリング方法を決定するには、高品質の技術ソリューションが必要です
    • リファクタリングプロセス中に発見され、リファクタリングの進行に影響を与える他の問題を回避します。
  4. コードの各行を注意深く検証し、責任を負います
  5. リファクタリング用に新しいファイルを作成し、以前の機能が使用できることを確認して、コードを段階的に置き換えます。

おすすめ

転載: blog.csdn.net/qq_28992047/article/details/132081532