通常我們在說的『元件』,其實指的是『元件實例/component instance』,只有實體使用的元件實例才有生命週期,以下提到的『元件』就是『元件實例』,因為講元件會比較順口。
元件實例的生命週期(lifecycle)
- 為什麼要了解元件實例的生命週期呢?
- 因為我們可以在定義程式碼並使用useEffect在上述特定時期來跑程式碼
為什麼不能在元件裡面更新state?
首先,在元件中宣告的state 以及 使用到asycn promise像是fetch function向第三方api請求資料等等的行為會在元件誕生時期(mounted)執行程式碼,因此在元件上面這區域也叫做render logic。
我們不能把會產生side effect的行為直接寫在render logic中,像是以下程式碼:
export default function App() {
const [watched, setWatched] = useState([])
fetch(`https://www.hhhhh`)
.then(res => res.json())
.then(data => setMovies(data.Search))
return (
<div>....</div>
)
}
- 回顧re-render會發生的情況:(並且當我們把side effect行為寫在render logic中)
- state改變時 ->
- setMovies()這裡代表state被改變了->re-render ->mounted ->發送fetch請求 ->當data回傳回來並解析成.json格式->setMovies()->state被改變->re-render ...
可以發現這裡的程式碼會使得side effect陷入無限get request的循環
- setMovies()這裡代表state被改變了->re-render ->mounted ->發送fetch請求 ->當data回傳回來並解析成.json格式->setMovies()->state被改變->re-render ...
- state改變時 ->
把產生side effect的程式碼註冊在useEffect hook中
當我們把像是上面這種會產生side effect的程式碼註冊在useEffect中,這個程式碼就可以在就可以在特定渲染/條件的情況下被執行,像是只要在initial render的情況下執行一次這種情況
整理上面程式碼註冊到useEffect中:
export default function App() {
const [watched, setWatched] = useState([])
useEffect(() => {
fetch(`https://www.hhhhh`)
.then(res => res.json())
.then(data => setMovies(data.Search))
}, [])
return (
<div>....</div>
)
}
- 當useEffect中的第二個參數依賴項/條件,是空陣列時,代表只會在mounted時,執行一次這個useEffect,這裡程式碼表示,當元件生命週期的初期(元件誕生/網頁生成),上面這個useEffect會執行裡面的程式碼,發出網路請求,並把得到的資料利用useState的setter function將資料更新。
- 上面這方式適合在小專案mounted時使用。
什麼是side effect?
- side effect基本上就是在一個React元件與元件以外的世界之間的互動,我們可以想成a side effect = 程式碼做了一些有用的行動發生,像是“data fetching from some api”, "setting up subscriptions","setting up timers","手動取得DOM"等等行為
這意謂著什麼?
這就表示,除了render logic(除了網頁渲染以及生成),我們會一直需要使用side effect,因為它讓我們的應用程式除了render logic之外,實際上還能做其他事,網路請求等等
當然就是在元件渲染時,不能同時發生side effect的事情,所以side effect的程式碼不能寫在render logic中。
有兩種方式可以寫side effect程式碼:
了解元件的生命週期其實有助於了解如何使用side effect,最好的方式是能用事件監聽器來寫side effect程式碼是最好,不要過度使用useEffect
加入async 與 await 在useEffect中
export default function App() {
const [watched, setWatched] = useState([])
useEffect(() => {
async function fetchMovies() {
const res = await fetch(`https://www.hhhhh`)
const data = await res.json()
setMovies(data.Search))
}
fetchMovie()
}, [])
return (
<div>....</div>
)
}
加入Loading state -> 對應的UI畫面
有時候網路很慢,為了讓使用者有『信心』表示正在請求伺服器的回覆請求資料,我們會很貼心的給予Loading state的畫面
首先,要把loading的自主權交給react -> controlled elements -> 設定loading的state ->
const [isLoading, seIstLoading] = useState(false);
export default function App() {
const [movies, setMovies] = useState([]);
const [watched, setWatched] = useState([]);
const [isLoading, seIstLoading] = useState(false);
const [error, setError] = useState(false);
const query = "bdfg";
useEffect(() => {
async function fetchMovies() {
try {
seIstLoading(true);
const res = await fetch(
`http://www.omdbapi.com/?apikey=${movie_key}&s=${query}`
);
//console.log(res); //會有一個response的物件 裡面有一個ok的屬性
if (!res.ok) throw new Error("fetch movie失敗了@@");
const data = await res.json();
// setMovies(data)
if (data.Response === "False")
throw new Error("找不到你要搜尋的電影,請檢查關鍵字是否有誤");
setMovies(data.Search);
// console.log(data);
// seIstLoading(false);
} catch (err) {
console.error(err.message); //這個錯誤物件中的message會是我們手動設定的throw new Error中的訊息
setError(err.message);
} finally {
//這裡的程式碼不管try 和catch 最後都會被執行
seIstLoading(false);
}
}
fetchMovies();
}, []);
return (
<>
<Navbar>
<Logo />
<Search />
<NumResult movies={movies} />
</Navbar>
<Main>
<Box>
{isLoading && <Loader />}
{!isLoading && !error && <MovieList movies={movies} />}
{error && <ErrorMsg message={error} />}
</Box>
<Box>
<WatchSummary watched={watched} />
<WatchMovieList watched={watched} />
</Box>
</Main>
</>
);
}
當我們進入useEffect中的程式碼,正在進入try塊中,會先將isLoading的狀態設定為true (表示,正在發送資料請求 所以會進入loading過程),當我們要把得到的資料存在movies的state中顯示到畫面上,這段過程就可以將isLoading的狀態設為false
至於在畫面的處理上的判斷就是三元運算子,後面還會加入錯誤處理
{isLoading ? <Loader /> : <MovieList movies={movies} />}
錯誤處理
在寫非同步網路請求時,一定要假設有錯誤情況發生來做適當的錯誤處理,像是失去網路連線
而在前面,我們有加入loading state來提示正在做網路請求等待資料回傳並渲染的狀態,但是像是網路斷線,使用者並不知道他失去網路連線,這時候的畫面仍然還是loading state,
我們要做的錯誤處理其實也是來引導使用者能夠面對不同情況作出有效的反應,這樣可以使讓使用者體驗升級。
因此要在適當的時機拋出錯誤因應機制,當錯誤發生 我們會想要投遞出error message 在螢幕上,這樣使用者才知道下一步能怎麼做
在上面的程式碼中,我們在fetch api時,得到回傳資料後,我們其實會收到一個Response的物件
console.log(res); //
會有一個response的物件 裡面有一個ok的屬性
實際上Response的物件 長這樣:
其中的body屬性,是我們需要的資料,會將它解析成.json格式,才會取得可以使用的data
我們先聚集注意在“ok”
這個屬性,true代表收到回傳的資料;false就是代表沒有成功得到回傳的資料 -> 利用這個屬性來手動拋出錯誤處理 ->拋到catch block中
然後為了要呈現在ui上 -> 遇到錯誤要讓react有掌控權可以自主處理 -> 設定controlled elements -> 設定error state ->
再來是設定元件畫面 並在畫面上依據情況做判斷
這裡比較複雜,做錯誤處理可以利用devtool 中的Network來做實驗顯現自己的製作效果