# react skill
## useState with callBack Function vs initialValue
```typescript
const [count, setCount] = useState(0)
const [count2, setCount2] = useState(0)
return (
<>
<button
onClick={() => {
// 每次只會加 1 ,因為 react 會根據 count 的值去加
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
}}
>add count</button>
<button
onClick={() => {
// 每次加 3
setCount2(pre => pre + 1)
setCount2(pre => pre + 1)
setCount2(pre => pre + 1)
}}
>add count2</button>
<div>count2 {count2}</div>
<div>count {count}</div>
</>
)
```
因為 `count` 永遠都是 0 ,所以對 react 來說在 batch 時候只會是 `0 + 1` 的結果,所以 `count` 只會加一 1 兒不是加三。
```typescript
setCount(count + 1)
setCount(count + 1)
setCount(count + 1)
```
但 `callBack` 則是不一樣,會是 `0 + 1` `1 + 1` `2 + 1`
```typescript
setCount2(pre => pre + 1)
setCount2(pre => pre + 1)
setCount2(pre => pre + 1)
```
## useEffect clean function觸發時間
```typescript=
useEffect(()=>{
return ()=>{
// clean up function
}
},[])
```
clean function執行的時機點有二(React 18)
1. state change
元件更新 → DOM 節點改變 → 畫面渲染 → 執行cleanup 函數 → 執行 Effect
2. unmount
元件更新 → DOM 節點改變 → 畫面渲染 → 執行cleanup 函數
備註:
react 17 以前 cleanup 函數是在畫面渲染前執行
1. state change
元件更新 → DOM 節點改變 → 執行cleanup 函數 → 畫面渲染 → 執行 Effect
2. unmount
元件更新 → DOM 節點改變 → 執行cleanup 函數 → 畫面渲染
## 什麼是Suspense

* Suspense 搭配React.lazy去做Code Splitting
* 當react render 到 profilePage時如果頁面未準備好,react會先暫停這個render profilePage 這時 react.lazy會先 throw Promise-like(Thenable),並render fallback UI,直到這個component準備好時才渲染。
### stream 架構中 suspense 處理
在 stream 架構中 server 端可以搭配 client 端的 suspense boundaries 去控制 error ,值得注意的是,如果你在 server 端發生 error 其實並不會去終止 server 端的渲染,取而代之的是,會將 suspense 的 fallBack UI 例如 loading spinner 在你的 initional html 中,但這的情況會導致一個問題是這樣的suspense結果會導致 client 端不會去觸發 error boundary的內容,原因是對 client 端來說他接收到的是一個 successfully 的 suspense 結果。

這樣的現象就變成 server 端造成的 error ,client端只會一直渲染 loading UI,而不是 error boundary 的 fallback 結果,為了解決這個問題,react 官方提供一個解法:
```typescript
<Suspense fallback={<Loading />}>
<Chat />
</Suspense>
function Chat() {
if (typeof window === 'undefined') {
throw Error('Chat should only render on the client.');
}
// ...
}
```
這樣你的 suspense 處理就會讓 client端去做,server 端就會跳過。
但這樣你的 `error 在 server side` 跟 `client side` 的 `error log` 會太多
### Server-side error:
```typescript
Error: Chat should only render on the client.",
"at Chat (/Users/Chat.tsx:86:19)
```
### Client-side error:
```typescript
Uncaught Error: Chat should only render on the client.
at updateDehydratedSuspenseComponent (react-dom.development.js:20662:1)
at updateSuspenseComponent (react-dom.development.js:20362:1)
at beginWork (react-dom.development.js:21624:1)
at beginWork$1 (react-dom.development.js:27426:1)
at performUnitOfWork (react-dom.development.js:26557:1)
at workLoopSync (react-dom.development.js:26466:1)
at renderRootSync (react-dom.development.js:26434:1)
at performConcurrentWorkOnRoot (react-dom.development.js:25738:1)
at workLoop (scheduler.development.js:266:1)
at flushWork (scheduler.development.js:239:1)
```
所以比較適合的寫法是透過 `useEffect` 的執行點判斷渲染的 `layout`
```typescript
const [client, setClient] = useState(false)
useEffect(() => {
setClient(true)
}, [])
if (!client) return null
return <Chat />
}
```
## 什麼是Hydration
* server side render 會先初始載入inital html,但這時候頁面還沒有辦法互動,react目前無法控制dom 節點,而Hydration 就是將 event listener等等的js綁到client上。
## 自動批次處理(Automatic Batching)
什麼事 batch
```typescript
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
const handleButtonClick = () => {
// 第一次呼叫時,將「取代為 1」這個動作加到待執行佇列中,
// 但還沒開始 re-render 也還沒真正更新 state 的值
setCount(1);
// 第二次呼叫時,將「取代為 2」這個動作加到待執行佇列中,
// 但還沒開始 re-render 也還沒真正更新 state 的值
setCount(2);
// 第三次呼叫時,將「取代為 3」這個動作加到待執行佇列中,
// 但還沒開始 re-render 也還沒真正更新 state 的值
setCount(3);
// 執行到這裡時,這個事件 callback 已經沒有後續的事情需要處理了,
// 此時就會開始統一進行一次 re-render,
// 並且依序試算 count state 的待執行佇列的結果:原值 => 取代為 1 => 取代為 2 => 取代為 3
// 因此最後會將 count state 的值直接更新成 3
};
// ...
}
```
當你執行 handleButtonClick 實際上 react 並不會直接修改 state 而是透過任務駐守方式執行,最後再統一 render 這叫做 automatic batching

## React <= 17
React 版本 <= 17 時也是有 batch 的但是是在同步情況,非同步不會。
```typescript
function App() {
const [count, setCount] = useState(0);
const [flag, setFlag] = useState(false);
function handleClick() {
fetchSomething().then(() => {
// React 17 以及更早的版本不會 batch 這些
setCount(c => c + 1); // 造成一次 re-render
setFlag(f => !f); // 造成一次 re-render
});
}
function handleClick() {
setCount(c => c + 1);
setFlag(f => !f);
// React 只會 re-render 一次
}
}
```
但如果是 setTimeout 會因為 eventLoop 關係造成多次 render 情況。
```typescript
setTimeout(
() => {
setCount(1);
setCount(2);
setCount(3);
// 此時會為了上面的三次 setState 分別依序進行 re-render,共三次 re-render,
// 無法自動支援 batch update
},
1000
);
```
甚至如果是 promise function react 在 useEffect 中會有 undefinded 問題
```typescript
export default function App() {
const [name, setname] = useState();
const [address, setaddress] = useState();
useEffect(() => {
console.log("useEffect run" + name, address);
}, [name, address]);
const handleCLick = () => {
setname("Leanne Graham");
setaddress({
street: "Kulas Light",
suite: "Apt. 556",
city: "Gwenborough",
zipcode: "92998-3874"
});
};
const asynchandleCLick = async () => {
const data = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
).then((res) => res.json());
console.log(data);
setname(data.name);
setaddress(data.address);
};
const reset = () => {
setname();
setaddress();
};
return (
<div className="App">
<p> name :{name}</p>
<p> address :{JSON.stringify(address, null, 2)}</p>
<button onClick={handleCLick}>click</button>
<button onClick={asynchandleCLick}>click async</button>
<button onClick={reset}>reset</button>
</div>
);
}
```
先試試看 `handleCLick` 一切都如預期,有自動 `batch`

但當我們執行 `asynchandleCLick` 時候 `useEffect` 第一次 console.log 的 `address` 是 `undefinded` 儘管我們 `fetch api` 有資料,然後 `useEffect` 又會在 `console.log` 一次這時 `address` 才會是有資料的,顯然非同步 `update state`會造成多次的 render 情況,這樣就沒有 `auto bactch`,而且也會造成不必要的 `render` 問題 。

## 解決方式 ( react 17 )
可以試著把多個 `state` 整合到一個 `useState` 中。
```typescript
const [userInfo,setUserInfo]=useState({
name:'',
address:{}
})
```
或是在 `react17` 可以用 `unstable_batchedUpdates` 把 `update state` 包進去 `callback function` ˋ中。
```typescript
import { unstable_batchedUpdates } from 'react-dom'
const asynchandleCLick = async () => {
unstable_batchedUpdates(()=>{
const data = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
).then((res) => res.json());
console.log(data);
setname(data.name);
setaddress(data.address);
})
// React 只會 re-render 一次
};
```
## REACT 18
但如果是 `18` 一切都沒有問題了
```typescript
function handleClick() {
fetchSomething().then(() => {
setCount(c => c + 1);
setFlag(f => !f);
});
// React 只會 re-render 一次
}
setTimeout(
() => {
setCount(1);
setCount(2);
setCount(3);
// 此時 React 會以 3 作為 setCount 的更新結果,只進行一次 re-render
},
1000
);
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Zet');
const handleClick = () => {
setCount(1);
setName('Foo');
setName('Bar');
setCount(2);
setCount(3);
// 以上的多次且混用的 setState 呼叫 總共只會導致觸發一次 re-render:
// 以 3 作為 count 的最後更新結果,且同時以 'Bar' 作為 name 的最後更新結果
};
// promise 也會 auto batching
const asynchandleCLick = async () => {
const data = await fetch(
"https://jsonplaceholder.typicode.com/users/1"
).then((res) => res.json());
console.log(data);
setname(data.name);
setaddress(data.address);
};
// ...
}
```
react 18解決無論在 timeout 、promise、native event handler 都會被Automatic Batching
https://chentsulin.medium.com/react-%E7%9A%84%E6%9C%AA%E4%BE%86-18-%E4%BB%A5%E5%8F%8A%E5%9C%A8%E9%82%A3%E4%B9%8B%E5%BE%8C-d5764e258deb
### 如果你不想 batch update 呢?
可以透過 flushSync 強制執行 react render
```typescript
// React 版本 >= 18
import { flushSync } from 'react-dom'; // 注意:是從 react-dom 裡 import,而不是 react
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Zet');
const willRenderThreeTime = () => {
flushSync(() => {
setCount(1)
})
// re render 一次
flushSync(() => {
setCount(2)
})
// re render 一次
flushSync(() => {
setCount(3)
})
// 總共re render 三次
}
const onlyRenderOnce = () => {
setCount(1)
setCount(2)
setCount(3)
setCount(pre => pre + 1)
setName(Math.random().toString())
setCount(pre => pre + 1)
}
// ...
}
```
## 新 Streaming 架構 Server-Side Rendering

以前ssr不支援 Suspense 造成 render hydration都要一步到位。也就是waterfall 現象,導致UIUX體驗不好。\
但新的Streaming架構,就可以使用Suspense,就不必等待緩慢的component載入好後才能渲染整個頁面,前端也可包含Spinner的html,透過Chunked Transfer 轉換用 inline script 把 spinner 替換掉 Comments 在page中render的位置

## Selective Hydration
跟前面提到的 waterfall ,以前Hydration 都要一步到位,在react 18 Selective Hydration 可以結合Suspense,
* 因為Comments還在準備inline script中視loading UI
* 這時 react 可以先處理NavBar等前面的的component的Hydration部分
* Comments準備好後將inline script中的loading UI 改成Comments,在做一次Hydration
Selective Hydration 可以大大提升 UIUX 的效能,大大減少waterfall問題,改善fcp 時間跟ttl的提前互動。

## Different Hydration Strategies
Partial Hydration
Partial Hydration 就是只 hydrate 需要互動的 components,其他靜態的部分就沒有必要去 hydrate。
Progressive Hydration
Progressive Hydration 則是 需要的時候才 hydrate,例如在 onClick, onFocus 或者元件出現在 viewport 之內。
### Concurrent feature
#### Transition
在整個 page 中不同的 component 彼此都有各自 render cost的時間,透過 react Transition 機制可以為這些 render去做排序,讓 high priority 的 component優先處理,減少畫面卡頓的狀況。


所有放到 startTransition 的 state update 都是低優先級的 component,同時可以加配 useTransition 提供的 callback 有 spinner效果

### 範例
#### before
例如以下的 onChange,因為auto batch的原因,所以 render 會等 setList 完成後才會觸發下一次的 render也就是 setFilter,畫面就會導致很卡,這時你可以使用 useTransition 解決這件事情。
```typescript
const [filter,setFilter]=useState()
const [list,setList]=useState()
let limit = 100000
const handleChange= (e)=>{
setFilter(e.targe.value)
const l = []
for(let i=0;i<=limit,i++){
l.push(e.target.value)
}
setList(l)
}
```
#### after
用 startTransition 包起來的 function 代表這個 state change的 priority 是比較低,所以 setList 會被 react 排到下一次的 render,所以 input 在頁面 onChange 變化就不會因為 setList 而 blocking the UI.,但值得注意的是 useTransition 不要亂用,原因是startTransition 是告訴 react 你要多做一次 render 的動作,所以在使用 useTransition 要記得評估多做一次的render是否可以解決 render卡頓問題,否則反而會增加效能負擔。
```typescript
const [isPending, startTransition] = useTransition()
const [filter,setFilter]=useState()
const [list,setList]=useState()
let limit = 100000
const handleChange= (e)=>{
setFilter(e.targe.value)
startTransition(()=>{
const l = []
for(let i=0;i<=limit,i++){
l.push(e.target.value)
}
setList(l)
})
}
```
## stream render
在 `server component` 中可以透過 `Suspense` 的 `fallback` 做 `loading`的顯示。
```typescript
import React from 'react'
const waitFor = () => new Promise((r) => setTimeout(r, 2000))
async function SuspenseCompoment() {
await waitFor()
return (
<div>Test</div>
)
}
export default SuspenseCompoment
import React, { Suspense } from 'react'
import SuspenseCompoment from './components/SuspenseCompoment'
async function ConversationsIdPage() {
return (
<Suspense fallback="loadig...">
{/* @ts-expect-error Server Component */}
<SuspenseCompoment />
</Suspense>
)
}
export default ConversationsIdPage
```
值得一提的是 `Next13` 中的 `page.tsx` 預設有包一層 `suspense` 然後 `loading` 可以透過添加 `loading.tsx` 顯示。

想分享一下最近面試 senior react 遇到的考題分享給大家~
1.setStae 是非同步還是同步。
2.React 18 的渲染流程怎麼觸發。
3.useEffect 裏面 callback 跟 clear function 的執行時間。
4.請解釋 suspense。
5.什麼事 batch update。
6.fiber 是什麼
7.什麼事 immuable state。
8.vue 跟 react 的 vdom 差異是什麼
## Don’t call Hooks inside loops, conditions, or nested functions.
因為 `loops` 、 `conditions` 、`nested function` 都有可能會造成 `react` 前後次的 `hook` 執行順序或數量不一致的問題,導致觸發 `Render more hooks then previous render ` 發生。



[來源](https://www.youtube.com/watch?v=o_7wgRHBzeA&t=60s)
## react render 時機點 (不考慮 StrictMode)
1. 第一次渲染 : adeb
2. 點擊 click : adecb
3. unmount : c
```typescript
import React, { useEffect, useState } from 'react'
export const Counter = () => {
const [count, setCount] = useState<number>(0)
console.log('a')
useEffect(() => {
console.log('b')
return () => {
console.log('c')
}
}, [count])
console.log('d')
return (
<div>
{console.log('e')}
<button onClick={() => setCount(count + 1)}>count</button>
</div>
)
}
```
## 重點 :
1. 初次渲染不執行 `clean function`
2. `state change` `useEffect` 執行順序 `clean function` 然後 `callback function`
3. `unmount` 只會執行 `clean function`
4. 每次 `render` 整個 `component` 都會 `console.log`
## Don't call react function component
https://kentcdodds.com/blog/dont-call-a-react-function-component
## hook
```typescript
import { useCallback, useEffect, useRef, useState } from 'react'
import { useCounter } from './useCounter'
interface UseTimerProps {
onTime: () => void
time: number
delay?: number
}
export const useTimer = ({ time, onTime, delay = 1000 }: UseTimerProps) => {
const callBackRef = useRef<() => void>()
const timerIdRef = useRef<NodeJS.Timeout>()
const { count, increment, reset } = useCounter(0)
useEffect(() => {
callBackRef.current = onTime
}, [onTime])
useEffect(() => {
timerIdRef.current = setInterval(() => {
if (count === time / 1000) {
callBackRef.current()
clearInterval(timerIdRef.current)
} else {
increment()
}
}, delay)
return () => {
clearInterval(timerIdRef.current)
}
}, [count, delay, increment, time])
return { count, reset }
}
const useCounter = (initionalValue: number) => {
const [count, setCount] = useState(initionalValue)
const increment = useCallback(() => setCount(pre => pre + 1), [])
const decrement = useCallback(() => setCount(pre => pre - 1), [])
const reset = useCallback(() => setCount(initionalValue), [])
return {
count,
increment,
decrement,
reset,
}
}
```
## useEffect vs useLayoutEffect
| | useEffect| useLayoutEffect|
| -------- | -------- | -------- |
| 執行時間 | render 後執行 | render 前執行 |
| async / sync | async | sync |
| 可否用在 ssr 中 | yes | no |
```typescript
import { useEffect, useLayoutEffect, useState } from 'react'
function App() {
const [isAdmin, setIsAdmin] = useState<boolean>(false)
const [useId, setuseId] = useState<number>(0)
const now = performance.now()
while (performance.now() - now < 300) { }
// 這邊把 useEffect 替換成 useLayoutEffect 會有不一樣的效果
useEffect(() => {
setIsAdmin(useId === 1)
}, [useId])
const handleChangeUser = () => {
const anotherUserId = 1
setuseId(anotherUserId)
}
return (
<>
<p>useEffect</p>
<p>useId: {useId}</p>
<p>isAdmin: {isAdmin ? 'true' : 'false'}</p>
<button onClick={handleChangeUser}>changeUser</button>
</>
)
}
export default App
```
因為 `useEffect` 他是 `non-block state update` 所以你會發現儘管 `userId` 改變了,`isAdmin` 並沒有立即渲染對應的結果,原因是 `useEffect` 的 `non-block` 特性不會等所有 `state update` 後才 `render` 完整的 `UI`,再加上 `component` 中的大量運算,會導致 `useEffect update state` 會有時間差 ,這可能會造成 `user` 畫面上的困惑。

取而代之你可以使用 `useLayoutEffect` ,他會等所有的 `state` 都 `update` 後才會 `render UI`,但你會發現 `UI` 結果會等一段時間後才會出現,但解決了 `state` 同步的需求。

#### 總結
所以如果你需要同步 `state` 在渲染 `UI` 的話可以使用 `useLayoutEffect` 但切記大多數情況下不太會去用它,可能會有額外的效能問題,如果你的 `component` 很肥大,使用 `useLayoutEffect` 的 `renter` 結果就會更久,所以謹慎使用~
### dom 操作的行為放到 useLayoutEffect 中
原因很簡單因為 `useLayoutEffect` 會在 `render` 前執行,可以可以確保 `layoutout` 不會跑版或是白畫面問題。
## SSR 不支援 useLayoutEffect
在 `useLayoutEffect` 因為不會在 `SSR` 執行所以可能會有非預期的 `hydration` 狀況發生,所以勁量避免。
```typescript
Warning: useLayoutEffect does nothing on the server, because its effect cannot be encoded into the server renderer's output format. This will lead to a mismatch between the initial, non-hydrated UI and the intended UI. To avoid this, useLayoutEffect should only be used in components that render exclusively on the client. See https://fb.me/react-uselayouteffect-ssr for common fixes.
```
解決方式就是使用 `customerEffect`
```typescript
import { useEffect, useLayoutEffect } from 'react';
const customerEffect = typeof window !== 'undefined' ?useLayoutEffect:useEffect
```
## react 18 useEffect mount 兩次
```typescript
useEffect(() => {
const getPost = async () => {
try {
const data = await fetch('https://jsonplaceholder.typicode.com/todos/1')
} catch (e) {
console.log(e)
}
}
getPost()
return () => {
// abortController.abort()
}
}, [])
```
在 react 18 中你會發現 `useEffect` 裡頭的 `request` 被執行兩次

原因其實是 `StrictMode` 關係,`useEffect` 會被 `remount` 兩次,官方是說 `StrictMode` 只會在 `dev` 中發生,`prod` 不會影響到,但這樣儘管是在 `dev` 中還是會有不必要的 `request` 問題發生。
```
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
```
### 解決方式
使用 `AbortController` 在 `unmount` 時 `abort`
```typescript
useEffect(() => {
const abortController = new AbortController()
const getPost = async () => {
try {
const data = await fetch('https://jsonplaceholder.typicode.com/todos/1', { signal: abortController.signal })
} catch (e) {
console.log(e)
}
}
getPost()
return () => {
abortController.abort()
}
}, [])
```
如此一來就可以減少 `request` 浪費摟~

## useSyncExternalStore reSubscrible
`useSyncExternalStore` 如果需要把 `subscribe function` 放到 `component` 中時記得要包 `useCallBack` 否則每次 `component rerender` 時都會重新 `re subscribe` ,容易造成效能問題。
```typescript
function ChatIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// 🚩 Always a different function, so React will resubscribe on every re-render
function subscribe() {
// ...
}
// ...
}
```
解決方式就是加上 `useCallBack`
```typescript
function ChatIndicator({ userId }) {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
// ✅ Same function as long as userId doesn't change
const subscribe = useCallback(() => {
// ...
}, [userId]);
// ...
}
```
或是只在 `hook`中使用 `useSyncExternalStore`
```typescript
export function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
return isOnline;
}
function getSnapshot() {
return navigator.onLine;
}
function getServerSnapshot() {
return true; // Always show "Online" for server-generated HTML
}
function subscribe(callback) {
// ...
}
```
## get Element props to component
```typescript
import React from 'react'
interface CardProps extends React.HTMLAttributes<HTMLElement> { }
export const Card = (props: CardProps) => {
return (
<div {...props}>AAA</div>
)
}
```
## Adjusting some state when a props changes
有時候我們可能會需要 `rest component` 中的 `state` ,在每次 `render` 的時候,需求會是每次 `update select` `comment` 會需要全部 `reset` 。

```typescript
function App() {
const [userId, setUerId] = useState<string>('')
return (
<>
<select name="" id="" onChange={e => setUerId(e.target.value)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<Profile userId={userId} />
</>
)
}
// ./Profile.tsx
import React, { useState } from 'react'
interface ProfileProps {
userId: string
}
export const Profile = ({
userId
}: ProfileProps) => {
const [comment, setComment] = useState<string>('')
console.log('Profile render')
return (
<>
<div>userId {userId}</div>
<p>comment : {comment}</p>
<input type="text" value={comment} onChange={e => setComment(e.target.value)} />
</>
)
}
```
但上面的 `code` 會有問題,儘管我們 `userId change` 觸發 `react render` 但我們的 `comment` 還是保留最後一次的內容沒有 `reset`。


正常情況下你的第一直覺可能會用 `useEffect` 的 `dependencies` 去監聽 `userId` 的變化然後 `reset comment`。
```typescript
// ./Profile.tsx
import React, { useState } from 'react'
interface ProfileProps {
userId: string
}
export const Profile = ({
userId
}: ProfileProps) => {
const [comment, setComment] = useState<string>('')
console.log('Profile render')
useEffect(() => {
setComment('')
}, [userId])
return (
<>
<div>userId {userId}</div>
<p>comment : {comment}</p>
<input type="text" value={comment} onChange={e => setComment(e.target.value)} />
</>
)
}
```
雖然現在可以 `reset` ,但其實可能會有淺在的效能問題。

#### you might not need useEffect
使用 `useEffect` 會重新觸發 `react rerender` ,也就是你的 `dom` 節點將會傳不重新 `calc` 與排序,重新跑一次 `reconciliation` ,這樣帶來的 `side effect` 會是如果你的 `Profile` 節點太肥,會因為 `reset` 關係導致所有 `dom` 都需要重新計算,效能會有問題。
解決方式就是把 `useEffect` 拿掉改用 `state` 如下:
透過 `preState` 的方式,去做 `reset` ,一方面是實現 `useEfect` 中呼叫`callBack` 的方式,同時也不會有效能上 `side Effect` 的問題。
```typescript
interface ProfileProps {
userId: string
}
export const Profile = ({
userId
}: ProfileProps) => {
const [comment, setComment] = useState<string>('')
const [preItems, setPreItems] = useState(userId)
if (preItems !== userId) {
setPreItems(userId)
setComment('')
}
return (
<>
<div>userId {userId}</div>
<p>comment : {comment}</p>
<input type="text" value={comment} onChange={e => setComment(e.target.value)} />
</>
)
}
```
但這樣可能還不是最佳解如果你的 `props` 更多那是不是也要用更多的 `preState` 去判斷,這時你可以考慮用 `key` 的方式。
```typescript
function App() {
const [userId, setUerId] = useState<string>('')
return (
<>
//...
<Profile userId={userId} key={userId} />
</>
)
}
```
同時 `Profile` 也不需要額外再判斷 `preState` `code` 是不是更乾淨了呢~
```typescript
import React, { useEffect, useState } from 'react'
interface ProfileProps {
userId: string
}
export const Profile = ({
userId
}: ProfileProps) => {
const [comment, setComment] = useState<string>('')
return (
<>
<div>userId {userId}</div>
<p>comment : {comment}</p>
<input type="text" value={comment} onChange={e => setComment(e.target.value)} />
</>
)
}
```
## useDeferredValue vs debounce
1. `useDeferredValue` 他不需要自己設定` debounce timeout` 會是根據 `user` 的當下環境自己決定 `debounce` 時間,設備好的 `debounce` 更快
1. 這個 `hook` 是直接綁定 `react` 的 `render` 生命週期
1. `useDeferredValue` 他的 `render` 是可以中斷的,而 `debounce` 則不行,什麼意思呢假設 `user` 在 `input` 輸入 3 次 ,` a->ab->abc` ,`denounce` 在 `ui` 上就會依序去 `render` ,但 `useDeferredValue` 只會 `render` 最後一次 `abc` 跟 `batch update` 很像。
[react 官網](https://react.dev/reference/react/useDeferredValue)
## use API
* 可以搭配 `context` 跟 `promise` 使用
* `use API` 可以用 `loop` 或是 `conditional statements`