【译】如安在React Hooks中猎取数据?

原文链接:
https://www.robinwieruch.de/r…

在本教程中,我想经由历程state和effect hook来像你展现怎样用React Hooks来猎取数据。我将会运用Hacker News的API来猎取热点的技术文章。你将会完成一个属于你自身的自定义hook来在你顺序的任何地方复用,或许是作为一个npm包宣布出来。

假如你还不晓得这个React的新特征,那末点击React Hooks引见,假如你想直接检察末了的完成效果,请点击这个github堆栈

注重:在将来,React Hooks将不会用于React的数据猎取,一个叫做Suspense的特征将会去担任它。但下面的教程仍会让你去更多的相识关于React中的state和effect hook。

用React Hooks去猎取数据

假如你对在React中猎取数据还不熟习,可以检察我其他的React猎取数据的文章。它将会指导你经由历程运用React的class组件来猎取数据,而且还可以和render props或许高阶组件一同运用,以及连系毛病处置惩罚和加载状况。在这篇文章中,我将会在function组件中运用React Hooks来展现这些功用。

import React, { useState } from 'react';

function App() {
  const [data, setData] = useState({ hits: [] });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

这个App组件展现了一个包含很多项的list(hits = Hacker News 文章)。state和state的更新函数来自于state hook中useState的挪用,它担任治理我们用来衬着list数据的当地状况,初始状况是一个空数组,此时还没有为其设置任何的状况。

我们将运用axios来猎取数据,固然你也可以运用其他的库或许fetch API,假如你还没装置axios,你可以在命令行运用npm install axios来装置它。然厥后完成用于数据猎取的effect hook:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await axios(
      'http://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
  });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

经由历程axios在useEffect中猎取数据,然后经由历程setData将数据放到组件当地的state中,并经由历程async/await来处置惩罚Promise。

然则当你运转顺序的时刻,你应当会碰到一个憎恶的轮回。effect hook不仅在组件mount的时刻也会在update的时刻运转。因为我们在每一次的数据猎取以后,会去经由历程setState设置状况,这时刻组件update然后effect就会运转一遍,这就造成了数据一次又一次的猎取。我们仅仅是想要在组件mount的时刻来猎取一次数据,这就是为何我们须要在useEffect的第二个参数供应一个空数组,从而完成只在mount的时刻触发数据猎取而不是每一次update。

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(async () => {
    const result = await axios(
      'http://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
  }, []);

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

第二个参数可以定义hooks所依靠的变量(在一个数组中去分派),假如一个变量转变了,hooks将会实行一次,假如是一个空数组的话,hooks将不会在组件更新的时刻实行,因为它没有监听到任何的变量。

这里另有一个圈套,在代码中,我们运用async/await从第三方的API中猎取数据,依据文档,每一个async函数都将返回一个promise,async函数声明定义了一个异步函数,它返回一个asyncFunction对象,异步函数是经由历程事宜轮回异步操纵的函数,运用隐式Promise返回其效果。然则,effect hook应当不返回任何内容或消灭功用,这就是为何你会在控制台看到以下正告:07:41:22.910 index.js:1452 Warning: useEffect function must return a cleanup function or nothing. Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect.. 这就是为何不允许在useEffect函数中直接运用async的缘由。让我们经由历程在effect内部运用异步函数来完成它的处理方案。

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'http://hn.algolia.com/api/v1/search?query=redux',
      );

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

简而言之,这就是用React Hooks猎取数据。然则,假如你对毛病处置惩罚、加载提醒、怎样从表单中触发数据猎取以及怎样完成可重用的数据猎取hook感兴趣,请继承浏览。

怎样经由历程编程体式格局/手动体式格局触发hook?

好的,我们在mount后猎取了一次数据,然则,假如运用input的字段来关照API哪一个话题是我们感兴趣的呢?“Redux”可以作为我们的默许查询,假如是关于“React”的呢?让我们完成一个input元素,使或人可以猎取“Redux”之外的话题。因而,为input元素引入一个新的状况。

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'http://hn.algolia.com/api/v1/search?query=redux',
      );

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

export default App;

如今,这两个状况相互自力,但如今愿望将它们耦合起来,以猎取由input中的输入来查询指定的项目。经由历程下面的变动,组件应当在挂载以后经由历程查询词猎取一切数据。

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    ...
  );
}

export default App;

还差一部份:当你尝试在input中输入一些内容时,在mount以后就不会再猎取任何数据了,这是因为我们供应了空数组作为第二个参数,effect没有依靠任何变量,因而只会在mount的时刻触发,然则如今的effect应当依靠query,每当query转变的时刻,就应当触发数据的猎取。

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [query]);

  return (
    ...
  );
}

export default App;

如今每当input的值更新的时刻便可以从新猎取数据了。但这又致使了另一个题目:关于input中键入的每一个字符,都邑触发该效果,并实行一个数据提取要求。怎样供应一个按钮来触发要求,从而手动hook呢?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [query]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="button" onClick={() => setSearch(query)}>
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

如今,effect依靠于于search,而不是随输入字段中变化的query。一旦用户点击按钮,新的search就会被设置,而且应当手动触发effect hook。

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${search}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [search]);

  return (
    ...
  );
}

export default App;

另外,search的初始值也设置为与query雷同,因为组件也在mount时猎取数据,因而效果应反应输入字段中的值。然则,具有相似的query和search状况有点令人困惑。为何不将实际的URL设置为状况而来替代search?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'http://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}

这就是运用effect hook猎取隐式编程数据的状况。你可以决议effect依靠于哪一个状况。一旦在点击或其他effect中设置此状况,此effect将再次运转。在这类状况下,假如URL状况发作变化,effect将再次运转以从API猎取数据。

React Hooks和loading

让我们为数据猎取引入一个加载提醒。它只是另一个由state hook治理的状况。loading被用于在组件中衬着一个loading提醒。

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'http://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);

      const result = await axios(url);

      setData(result.data);
      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

一旦挪用该effect举行数据猎取(当组件mount或URL状况变动时发作),加载状况将设置为true。一旦要求完成,加载状况将再次设置为false。

React Hooks和毛病处置惩罚

假如在React Hooks中加上毛病处置惩罚呢,毛病只是用state hook初始化的另一个状况。一旦涌现毛病状况,运用顺序组件便可认为用户供应反应。运用async/await时,一般运用try/catch块举行毛病处置惩罚。你可以在effect内做到:

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'http://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      {isError && <div>Something went wrong ...</div>}

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

React在表单中猎取数据

到如今为止,我们只要input和按钮的组合。一旦引入更多的输入元素,您能够须要用一个表单元素包装它们。另外,表单还可以经由历程键盘上的“enter”来触发。

function App() {
  ...

  return (
    <Fragment>
      <form
        onSubmit={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError && <div>Something went wrong ...</div>}

      ...
    </Fragment>
  );
}

然则如今浏览器在单击提交按钮时页面会从新加载,因为这是浏览器在提交表单时的固有行动。为了防备默许行动,我们可以经由历程event.preventDefault()作废默许行动。这也是在React类组件中完成的要领。

function App() {
  ...

  const doFetch = () => {
    setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);
  };

  return (
    <Fragment>
      <form onSubmit={event => {
        doFetch();

        event.preventDefault();
      }}>
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError && <div>Something went wrong ...</div>}

      ...
    </Fragment>
  );
}

如今,当你单击提交按钮时,浏览器不会再从新加载。它和之前一样事情,但这次运用的是表单,而不是简朴的input和按钮组合。你也可以按键盘上的“回车”键。

自定义数据猎取hook

为了提取用于数据猎取的自定义hook,请将属于数据猎取的一切内容,移动到一个自身的函数中。还要确保可以返回App组件所须要的悉数变量。

const useHackerNewsApi = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState(
    'http://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  const doFetch = () => {
    setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);
  };

  return { data, isLoading, isError, doFetch };
}

如今,你可以在App组件中运用新的hook了。

function App() {
  const [query, setQuery] = useState('redux');
  const { data, isLoading, isError, doFetch } = useHackerNewsApi();

  return (
    <Fragment>
      ...
    </Fragment>
  );
}

接下来,从dofetch函数外部通报URL状况:

const useHackerNewsApi = () => {
  ...

  useEffect(
    ...
  );

  const doFetch = url => {
    setUrl(url);
  };

  return { data, isLoading, isError, doFetch };
};

function App() {
  const [query, setQuery] = useState('redux');
  const { data, isLoading, isError, doFetch } = useHackerNewsApi();

  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );

          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      ...
    </Fragment>
  );
}

初始状况也可以变成通用状况。把它简朴地通报给新的自定义hook:

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  const doFetch = url => {
    setUrl(url);
  };

  return { data, isLoading, isError, doFetch };
};

function App() {
  const [query, setQuery] = useState('redux');
  const { data, isLoading, isError, doFetch } = useDataApi(
    'http://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );

  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );

          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError && <div>Something went wrong ...</div>}

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;

这就是运用自定义hook猎取数据的要领。hook自身对API一窍不通。它从外部吸收一切参数,只治理必要的状况,如数据、加载和毛病状况。它实行要求并将数据作为自定义数据猎取hook返回给组件。

Reducer的数据猎取hook

reducer hook返回一个状况对象和一个转变状况对象的函数。dispatch函数吸收type和可选的payload。一切这些信息都在实际的reducer函数中运用,从之前的状况、包含可选payload和type的action中提取新的状况。让我们看看这在代码中是怎样事情的:

import React, {
  Fragment,
  useState,
  useEffect,
  useReducer,
} from 'react';
import axios from 'axios';

const dataFetchReducer = (state, action) => {
  ...
};

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  ...
};

Reducer Hook接收reducer函数和一个初始化的状况对象作为参数,在我们的例子中,数据、加载和毛病状况的初始状况的参数没有转变,然则它们被聚合到由一个reducer hook治理的一个状况对象,而不是单个state hook。

const dataFetchReducer = (state, action) => {
  ...
};

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await axios(url);

        dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };

    fetchData();
  }, [url]);

  ...
};

如今,在猎取数据时,可以运用dispatch向reducer函数发送信息。dispatch函数发送的对象包含一个必填的type属性和可选的payload。type关照Reducer函数须要运用哪一个状况转换,而且Reducer还可以运用payload来提取新状况。毕竟,我们只要三种状况转换:初始化猎取历程,关照胜利的数据猎取效果,以及关照毛病的数据猎取效果。

在自定义hook的末了,状况像之前一样返回,然则因为我们有一个状况对象,而不再是自力状况,所以须要用扩大运算符返回state。如许,挪用useDataApi自定义hook的用户依然可以接见data、isloading和isError:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  ...

  const doFetch = url => {
    setUrl(url);
  };

  return { ...state, doFetch };
};

末了,还缺少了reducer函数的完成。它须要处置惩罚三种差别的状况转换,即FETCH_INIT、FETCH_SUCCESS和FETCH_FAILURE。每一个状况转换都须要返回一个新的状况对象。让我们看看怎样用switch case语句完成这一点:

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return { ...state };
    case 'FETCH_SUCCESS':
      return { ...state };
    case 'FETCH_FAILURE':
      return { ...state };
    default:
      throw new Error();
  }
};

reducer函数可以经由历程其参数接见当前状况和action。到如今为止,switch case语句中的每一个状况转换只会返回本来的状况。...语句用于坚持状况对象稳定(意味着状况永久不会直接转变),如今,让我们重写一些当前状况返回的属性,以便在每次状况转换时变动状况:

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        isLoading: true,
        isError: false
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: action.payload,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        isLoading: false,
        isError: true,
      };
    default:
      throw new Error();
  }
};

如今,每一个状况转换(由操纵的type决议)都将基于先前的状况和可选的payload返回一个新的状况。比方,在胜利要求的状况下,payload用于设置新状况对象的数据。

总之,reducer hook确保状况治理的这一部份是用自身的逻辑封装的。经由历程供应type和可选payload,你将一直已一个可展望的状况完毕。另外,你将永久不会进入无效状况。比方,之前能够会心外埠将isloading和isError状况设置为true。在这个案例的用户界面中应当显现什么?如今,reducer函数定义的每一个状况转换都邑致使一个有效的状况对象。

在effect hook中制止数据猎取

纵然组件已卸载(比方,因为运用react路由器导航而脱离),设置组件状况也是react中的一个常见题目。我之前在这里写过这个题目,它形貌了怎样防备在种种场景中为unmount的组件设置状况。让我们看看怎样防备在自定义hook中为数据猎取设置状况:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await axios(url);

        if (!didCancel) {
          dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: 'FETCH_FAILURE' });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  const doFetch = url => {
    setUrl(url);
  };

  return { ...state, doFetch };
};

每一个effect hook都有一个clean功用,在组件卸载时运转。clean函数是从hook返回的一个函数。在我们的例子中,我们运用一个名为didCancel的布尔标志,让我们的数据猎取逻辑晓得组件的状况(已装载/未装载)。假如组件已卸载,则标志应设置为“tree”,这将致使在终究异步处理数据提取后没法设置组件状况。

注重:事实上,数据猎取不会中断——这可以经由历程axios的Cancellation完成——然则关于未装置的组件,状况转换会不再实行。因为在我看来,axios的Cancellation并非最好的API,所以这个防备设置状况的布尔标志也能起到作用。

你已相识了在React中state和effect hook怎样用于猎取数据。假如您对运用render props和高阶组件在类组件(和函数组件)中猎取数据很感兴趣,请从一最先就去我的另一篇文章。不然,我愿望本文对您相识react hook以及怎样在实际场景中运用它们异常有效。

    原文作者:玩弄心里的鬼
    原文地址: https://segmentfault.com/a/1190000018729036
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞