中文字幕一区二区人妻电影,亚洲av无码一区二区乱子伦as ,亚洲精品无码永久在线观看,亚洲成aⅴ人片久青草影院按摩,亚洲黑人巨大videos

使用React Hooks和Typescript獲取數(shù)據(jù)

發(fā)布于:2021-02-04 12:00:20

0

815

0

react javascript typescript React Hooks

在React中重用邏輯是很復(fù)雜的,像HOCs和Render Props這樣的模式試圖解決這個問題。隨著最近添加的hooks,重用邏輯變得更加容易。在本文中,我將展示一種使用hooks useEffectuseState從web服務(wù)加載數(shù)據(jù)的簡單方法(我正在使用斯瓦皮公司在裝載星戰(zhàn)星際飛船的例子中)以及如何輕松管理裝載狀態(tài)。作為獎勵,我用的是打字。我將建立一個簡單的應(yīng)用程序來買賣星球大戰(zhàn)星際飛船,你可以在這里看到最終的結(jié)果https://camilosw.github.io/react-hooks-services。

加載初始數(shù)據(jù)

在Reacthooks發(fā)布之前,從web服務(wù)加載初始數(shù)據(jù)的最簡單方法是componentDidMount

class Starships extends React.Component {
 state = {
   starships: [],
   loading: true,
   error: false
 }

 componentDidMount () {
   fetch('https://swapi.co/api/starships')
     .then(response => response.json())
     .then(response => this.setState({
       starships: response.results,
       loading: false
     }))
     .catch(error => this.setState({
       loading: false,
       error: true
     }));
 }

 render () {
   const { starships, loading, error } = this.state;
   return ({loading &&Loading...}
       {!loading && !error &&
         starships.map(starship => ({starship.name}))
       }
       {error &&Error message});
 }
};


但是重用這些代碼是很困難的,因為您無法從React 16.8之前的組件中提取行為。流行的選擇是使用高階組件或渲染道具,但是這些方法有一些缺點,如React Hooks文檔中所述https://reactjs.org/docs/hooks intro.html-組件間難以重用的有狀態(tài)邏輯。

使用hooks,我們可以將行為提取到自定義hooks中,這樣就可以在任何組件中輕松地重用它。如果您不知道如何創(chuàng)建自定義掛鉤,請先閱讀文檔:https://reactjs.org/docs/hooks-custom.html。

因為我們使用的是Typescript,首先我們需要定義我們希望從web服務(wù)接收的數(shù)據(jù)的形狀,所以我定義了接口Starship

export interface Starship {
 name: string;
 crew: string;
 passengers: string;
 cost_in_credits?: string;
 url: string;
}

因為我們將處理具有多個狀態(tài)的web服務(wù),所以我為每個狀態(tài)定義了一個接口。最后,我將Service定義為這些接口的聯(lián)合類型:

interface ServiceInit {
 status: 'init';
}
interface ServiceLoading {
 status: 'loading';
}
interface ServiceLoaded{
 status: 'loaded';
 payload: T;
}
interface ServiceError {
 status: 'error';
 error: Error;
}
export type Service=
 | ServiceInit
 | ServiceLoading
 | ServiceLoaded| ServiceError;


ServiceInitServiceLoading分別定義任何操作之前和加載時web服務(wù)的狀態(tài)。ServiceLoaded具有屬性payload來存儲從web服務(wù)加載的數(shù)據(jù)(請注意,我在這里使用的是泛型,因此我可以將該接口與有效負(fù)載的任何數(shù)據(jù)類型一起使用)。ServiceError具有屬性error來存儲可能發(fā)生的任何錯誤。使用此聯(lián)合類型,如果我們在status屬性中設(shè)置字符串'loading',并嘗試為payloaderror屬性賦值,則Typescript將失敗,因為我們沒有定義一個接口來允許status類型'loading'與名為payloaderror的屬性一起使用。如果不進行Typescript或任何其他類型檢查,代碼只有在運行時出錯時才會失敗。

定義了類型Service和接口Starship之后,現(xiàn)在可以創(chuàng)建自定義hooksusePostStarshipService

import { useEffect, useState } from 'react';
import { Service } from '../types/Service';
import { Starship } from '../types/Starship';

export interface Starships {
 results: Starship[];
}

const usePostStarshipService = () => {
 const [result, setResult] = useState<Service>({
   status: 'loading'
 });

 useEffect(() => {
   fetch('https://swapi.co/api/starships')
     .then(response => response.json())
     .then(response => setResult({ status: 'loaded', payload: response }))
     .catch(error => setResult({ status: 'error', error }));
 }, []);

 return result;
};

export default usePostStarshipService;

這是在前面的代碼中發(fā)生的:

  • 因為SWAPI在數(shù)組中返回一個星際飛船數(shù)組,所以我定義了一個新的接口,它包含屬性results,作為一個數(shù)組的Starship。

  • 自定義hooksusePostStarshipService只是一個函數(shù),從文檔中建議的單詞use開始:https://reactjs.org/docs/hooks custom.html-一個定制hooks。

  • 在該函數(shù)內(nèi)部,我正在使用HookuseState來管理Web服務(wù)狀態(tài)。注意,我需要定義將由result傳遞通用狀態(tài)的狀態(tài)管理的確切數(shù)據(jù)類型<Service<Starship>>。我正在用ServiceInit聯(lián)合類型的接口初始化Hook Service,所以唯一允許的屬性是status字符串'loading'。

  • 我還使用useEffect帶有回調(diào)的Hook作為第一個參數(shù)來從Web服務(wù)中獲取數(shù)據(jù),并使用空數(shù)組作為第二個參數(shù)。第二個參數(shù)告訴useEffect您執(zhí)行回調(diào)的條件是什么,并且因為我們傳遞的是空數(shù)組,所以該回調(diào)將僅被調(diào)用一次(有關(guān)useEffect您是否不熟悉Hook的更多信息,請參見https://reactjs.org/docs /hooks-effect.html)。

  • 最后,我要返回result。該對象包含狀態(tài)以及由于調(diào)用Web服務(wù)而導(dǎo)致的任何有效負(fù)載或錯誤。這就是我們在組件中向用戶顯示W(wǎng)eb服務(wù)狀態(tài)和檢索到的數(shù)據(jù)所需要的。

請注意,我在上一個示例中使用的fetch方法非常簡單,但對于生產(chǎn)代碼來說還不夠。例如,catch只捕獲網(wǎng)絡(luò)錯誤,而不是4xx或5xx錯誤。在您自己的代碼中,最好創(chuàng)建另一個包裝fetch的函數(shù)來處理錯誤、標(biāo)題等。

現(xiàn)在,我們可以使用我們的hooks來檢索星際飛船列表并將它們顯示給用戶:

們使用的是Typescript,首先我們需要定義我們希望從web服務(wù)接收的數(shù)據(jù)的形狀,所以我定義了接口Starship

import React from 'react';
import useStarshipsService from '../services/useStarshipsService';

const Starships: React.FC<{}> = () => {
 const service = useStarshipsService();

 return (
   <div>
     {service.status === 'loading' && <div>Loading...</div>}
     {service.status === 'loaded' &&
       service.payload.results.map(starship => (
         <div key={starship.url}>{starship.name}</div>
       ))}
     {service.status === 'error' && (
       <div>Error, the backend moved to the dark side.</div>
     )}
   </div>
 );
};

export default Starships;

這次,我們的自定義hooks將管理狀態(tài),因此我們只需要根據(jù)返回的service對象的status屬性有條件地呈現(xiàn)。

請注意,如果在狀態(tài)為'loading'時嘗試訪問payload,TypeScript將失敗,因為payload只存在于ServiceLoaded接口中,而不存在于ServiceLoading接口中:

TypeScript非常聰明,知道如果status屬性和字符串'loading'之間的比較為真,則相應(yīng)的接口是ServiceLoaded,在這種情況下,starships對象沒有payload屬性。

狀態(tài)更改時加載內(nèi)容

在我們的示例中,如果用戶單擊任何星艦,我們將更改組件上的狀態(tài)以設(shè)置所選的星艦,并使用與該星艦對應(yīng)的url調(diào)用web服務(wù)(注意https://swapi.co/api/starships加載每艘星際飛船的所有數(shù)據(jù),因此無需再次加載該數(shù)據(jù)。我這樣做只是為了演示。)

傳統(tǒng)上,我們使用componentdiddupdate來檢測狀態(tài)變化并執(zhí)行相應(yīng)的操作:

class Starship extends React.Component {
 ...
 componentDidUpdate(prevProps) {
   if (prevProps.starship.url !== this.props.starship.url) {
     fetch(this.props.starship.url)
       .then(response => response.json())
       .then(response => this.setState({
         starship: response,
         loading: false
       }))
       .catch(error => this.setState({
         loading: false,
         error: true
       }));
   }
 }
 ...};

如果我們需要在不同的道具和狀態(tài)屬性發(fā)生變化時做出不同的動作,componentDidUpdate很快就會變得一團糟。使用hooks,我們可以將這些操作封裝在單獨的自定義hooks中。在本例中,我們將創(chuàng)建一個自定義hooks來提取componentDidUpdate中的行為,就像我們之前所做的那樣:

import { useEffect, useState } from 'react';
import { Service } from '../types/Service';
import { Starship } from '../types/Starship';

const useStarshipByUrlService = (url: string) => {
 const [result, setResult] = useState<Service<Starship>>({
   status: 'loading'
 });

 useEffect(() => {
   if (url) {
     setResult({ status: 'loading' });
     fetch(url)
       .then(response => response.json())
       .then(response => setResult({ status: 'loaded', payload: response }))
       .catch(error => setResult({ status: 'error', error }));
   }
 }, [url]);

 return result;
};

export default useStarshipByUrlService;

這一次,我們的自定義hooks接收url作為參數(shù),并將其用作hooks的第二個參數(shù)。這樣,每當(dāng)url改變時,就會調(diào)用useEffect中的回調(diào)來檢索新星際飛船的數(shù)據(jù)。

注意,在回調(diào)中,我調(diào)用setResultstatus設(shè)置為'loading'。這是因為回調(diào)將被多次調(diào)用,所以我們需要在開始獲取之前重置狀態(tài)。

在我們的Starship組件中,我們將url作為一個道具接收,并將其傳遞給我們的定制hooksuseStarshipByUrlService。每當(dāng)父組件中的url發(fā)生更改時,我們的自定義hooks將再次調(diào)用web服務(wù)并為我們管理狀態(tài):

import React from 'react';
import useStarshipByUrlService from '../services/useStarshipByUrlService';

export interface Props {
 url: string;
}

const Starship: React.FC<Props> = ({ url }) => {
 const service = useStarshipByUrlService(url);

 return (
   <div>
     {service.status === 'loading' && <div>Loading...</div>}
     {service.status === 'loaded' && (
       <div>
         <h2>{service.payload.name}</h2>
         ...
       </div>
     )}
     {service.status === 'error' && <div>Error message</div>}
   </div>
 );
};

export default Starship;

正在發(fā)送內(nèi)容

發(fā)送內(nèi)容類似于在狀態(tài)更改時加載內(nèi)容。在第一種情況下,我們向自定義hooks傳遞了一個url,現(xiàn)在我們可以傳遞一個包含要發(fā)送的數(shù)據(jù)的對象。如果我們嘗試這樣做,代碼將是這樣的:

const usePostStarshipService = (starship: Starship) => {
 const [result, setResult] = useState<Service<Starship>>({
   status: 'init'
 });

 useEffect(() => {
   setResult({ status: 'loading' });
   fetch('https://swapi.co/api/starships', {
     method: 'POST',
     body: JSON.stringify(starship)
   })
     .then(response => response.json())
     .then(response => {
       setResult({ status: 'loaded', payload: response });
     })
     .catch(error => {
       setResult({ status: 'error', error });
     });
 }, [starship]);

 return result;
};

const CreateStarship: React.FC<{}> = () => {
 const initialStarshipState: Starship = {
   name: '',
   crew: '',
   passengers: '',
   cost_in_credits: ''
 };
 const [starship, setStarship] = useState<PostStarship>(initialStarshipState);
 const [submit, setSubmit] = useState(false);
 const service = usePostStarshipService(starship);

 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
   event.persist();
   setStarship(prevStarship => ({
     ...prevStarship,
     [event.target.name]: event.target.value
   }));
 };

 const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
   event.preventDefault();
   setSubmit(true);
 };

 useEffect(() => {
   if (submit && service.status === 'loaded') {
     setSubmit(false);
     setStarship(initialStarshipState);
   }
 }, [submit]);

 return (
   <form onSubmit={handleFormSubmit}>
     <input
       type="text"
       name="name"
       value={starship.name}
       onChange={handleChange}
     />
     ...
   </form>
 )
}

但之前的代碼有一些問題:

  • 我們將starship對象傳遞給自定義hooks,并將該對象作為useEffecthooks的第二個參數(shù)傳遞。因為onChange處理程序會在每次擊鍵時更改starship對象,所以每次用戶鍵入時都會調(diào)用我們的web服務(wù)。

  • 我們需要使用hooksuseState來創(chuàng)建布爾狀態(tài)submit只知道何時可以清理表單。我們可以使用這個布爾值作為usePostStarshipService的第二個參數(shù)來解決前面的問題,但這會使我們的代碼復(fù)雜化。

  • 布爾值狀態(tài)submit為我們的組件添加了邏輯,這些邏輯必須復(fù)制到重用我們的自定義hooks的其他組件上usePostStarshipService

有一個更好的方法,這次沒有useEffecthooks

import { useState } from 'react';
import { Service } from '../types/Service';
import { Starship } from '../types/Starship';

export type PostStarship = Pick<
 Starship,
 'name' | 'crew' | 'passengers' | 'cost_in_credits'
>;

const usePostStarshipService = () => {
 const [service, setService] = useState<Service<PostStarship>>({
   status: 'init'
 });

 const publishStarship = (starship: PostStarship) => {
   setService({ status: 'loading' });

   const headers = new Headers();
   headers.append('Content-Type', 'application/json; charset=utf-8');

   return new Promise((resolve, reject) => {
     fetch('https://swapi.co/api/starships', {
       method: 'POST',
       body: JSON.stringify(starship),
       headers
     })
       .then(response => response.json())
       .then(response => {
         setService({ status: 'loaded', payload: response });
         resolve(response);
       })
       .catch(error => {
         setService({ status: 'error', error });
         reject(error);
       });
   });
 };

 return {
   service,
   publishStarship
 };
};

export default usePostStarshipService;

首先,我們創(chuàng)建了一個新的PostStarship類型,它派生自Starship,選擇將發(fā)送到web服務(wù)的屬性。在我們的自定義hooks中,我們使用屬性status中的字符串'init'初始化服務(wù),因為調(diào)用時usePostStarshipService不會對web服務(wù)做任何操作。這次我們沒有使用useEffecthooks,而是創(chuàng)建了一個函數(shù),它將接收要發(fā)送到web服務(wù)的表單數(shù)據(jù)并返回一個承諾。最后,我們返回一個帶有service對象的對象和負(fù)責(zé)調(diào)用web服務(wù)的函數(shù)。

注意:我可以返回一個數(shù)組而不是自定義hooks中的一個對象,使其行為類似于useStatehooks,這樣就可以任意命名組件中的名稱。我決定返回一個對象,因為我認(rèn)為沒有必要重命名它們。如果愿意,您可以自由地返回數(shù)組。

我們的CreateStarship組件這次將更簡單:

import React, { useState } from 'react';
import usePostStarshipService, {
 PostStarship
} from '../services/usePostStarshipService';
import Loader from './Loader';

const CreateStarship: React.FC<{}> = () => {
 const initialStarshipState: PostStarship = {
   name: '',
   crew: '',
   passengers: '',
   cost_in_credits: ''
 };
 const [starship, setStarship] = useState<PostStarship>(
   initialStarshipState
 );
 const { service, publishStarship } = usePostStarshipService();

 const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
   event.persist();
   setStarship(prevStarship => ({
     ...prevStarship,
     [event.target.name]: event.target.value
   }));
 };

 const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
   event.preventDefault();
   publishStarship(starship).then(() => setStarship(initialStarshipState));
 };

 return (
   <div>
     <form onSubmit={handleFormSubmit}>
       <input
         type="text"
         name="name"
         value={starship.name}
         onChange={handleChange}
       />
       ...
     </form>
     {service.status === 'loading' && <div>Sending...</div>}
     {service.status === 'loaded' && <div>Starship submitted</div>}
     {service.status === 'error' && <div>Error message</div>}
   </div>
 );
};

export default CreateStarship;

我正在使用useStatehooks來管理窗體的狀態(tài),但是handleChange的行為與使用this.state類內(nèi)組件時的行為相同。我們的usePostStarshipService除了返回處于初始狀態(tài)的service對象并返回publishStarship方法來調(diào)用web服務(wù)之外,什么都不做。提交表單并調(diào)用handleFormSubmit時,我們使用表單數(shù)據(jù)調(diào)用publishStarship?,F(xiàn)在,我們的service對象開始管理web服務(wù)更改的狀態(tài)。如果返回的承諾成功,我們用initialStarshipState調(diào)用setStarship來清理表單。

僅此而已,我們有三個自定義hooks來檢索初始數(shù)據(jù)、檢索單個項和發(fā)布數(shù)據(jù)。您可以在這里看到完整的項目:https://github.com/camilosw/react-hooks-services

最后的想法

Reacthooks是一個很好的補充,但是當(dāng)有更簡單和完善的解決方案時,不要試圖過度使用它們,比如Promise,而不是我們發(fā)送內(nèi)容示例中的useEffect。

使用hooks還有另一個好處。如果你仔細看,你會發(fā)現(xiàn)我們的組件基本上是呈現(xiàn)的,因為我們把有狀態(tài)邏輯移到了定制的hooks上。有一個已建立的模式將邏輯與表示分離,稱為容器/表示,您將邏輯放在父組件中,將表示放在子組件中。這種模式最初是由丹·阿布拉莫夫構(gòu)思的,但現(xiàn)在我們有了hooks,丹·阿布拉莫夫建議少用這種模式,而使用hooks:https://medium.com/@dan_abramov/smart-和-dumb-components-7ca2f9a7c7d0。

也許你討厭使用字符串來命名狀態(tài),并責(zé)怪我這么做,但如果你使用Typescript,你是安全的,因為Typescript將失敗,如果你拼寫錯誤的狀態(tài)名稱,你將得到自動完成免費在VS代碼(和其他編輯器可能)。不管怎樣,如果你喜歡的話,你可以用布爾。