# React Concepts [2018發表Hook](https://www.youtube.com/watch?v=dpw9EHDh2bM) #### React 合成事件機制 關鍵字:Batching [谷歌分享的連結](https://github.com/reactwg/react-18/discussions/46#discussioncomment-846694) #### 避免Anit-Pattern 幫Sharon解googleMap遇到的兩個反模式: 1. 串接多個useEffect,依賴前一個去計算,最後只用最後一個useEffect的值去render 2. 在useEffect裡用Array loop method去執行async/await 的call-back,其實是快速的丟了三個async function,處理起來並不是同步的 若想強制同步,要使用for loop(是強制同步逐行跑的概念) 但怕等很久,可以用map+promise.all折衷,解法: ``` const [latLngArr, setLatLngArr] = useState<LatLng[]>([]); useEffect(() => { const fetchDataAndSetLatLngArr = async () => { const barsCollectionRef = collection(db, "bars"); const data = await getDocs(barsCollectionRef); const bars = data.docs.map((doc) => ({ ...(doc.data() as IBar), id: doc.id, })); const address = bars.map((bar) => bar.address); const latLngPromises = address.map((address) => fetchData(address)); const latLngArr = await Promise.all(latLngPromises); setLatLngArr(latLngArr); }; fetchDataAndSetLatLngArr(); }, []); ``` #### react針對拼音輸入法處理: [Composition Events](https://kuro.tw/posts/2016/10/11/%E7%AD%86%E8%A8%98-%E9%80%8F%E9%81%8E-Composition-Events-%E5%A2%9E%E5%BC%B7%E9%9D%9E%E6%8B%89%E4%B8%81%E8%AA%9E%E7%B3%BB%E8%BC%B8%E5%85%A5%E6%B3%95%E5%B0%8D%E8%BC%B8%E5%85%A5%E6%A1%86%E7%9A%84%E6%94%AF%E6%8F%B4/) | [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event) #### [React Docs](https://beta.reactjs.org/learn/choosing-the-state-structure):的Learn React文章含金量很高 #### 一定要放到useEffect裡的事 可以把useEffect想像成window.eventListener 1. 打API: useEffect 是唯一能控制時機跟次數的地方 #### 有control的input input通常是用onChange來觸發事件,若要讓input被state控制,value屬性必須跟state有關,才能用onChange去控制 #### `useState()`/`useReducer`裡放callback的 ``` const [ state , setState ] = useState(()=>{產生值的function,如fetch}); ``` 裡面放callback funciton時react會知道只需要在第一次用這個callback. ``` const [ state , dispatch ] = useReducer(reducer,null,()=>{產生值的function,如fetch}); ``` reducer的用法,要放在第三個參數,第二個參數會被變成callback的arg. #### 第一次render要避免沒資料的狀 加一個if判斷式 ``` if (!products) { console.log("not data"); return null; //return undefined; //return ''; //return <></>; //return; } ``` #### JSX可以讀取array,所以product.map生成的JSX可以不用一個一個取出,可以包在[]裡直接return * forEach回傳值是unedfined,所以直接用不行,但可以先宣告一個`變數=[]`,然後生成的jsx內容再push進array * map的JSX如果沒有給key,預設會用index去給 #### 一個function不會片段執行,所以重新渲染都是整個function component重新執行 #### function Component 裡不要再宣告一個function Component,會有效能問題(因為每次render都會重新宣告裡面的component) ### Cart設計 ``` function handleAddtoCart() { if (selectedColor !== undefined && selectedSize !== null && quantity !== 0) { console.log('Click ADDTOCART!!'); const item = { productId: product.id, color: product.colors[selectedColor], size: product.sizes[selectedSize], quantity, }; const cart = JSON.parse(localStorage.getItem('cart')) || []; const index = cart.findIndex( (cartItem) => cartItem.productId === item.productId && cartItem.color.code === item.color.code && cartItem.size === item.size ); if (index === -1) { cart.push(item); } else { cart[index].quantity += item.quantity; } localStorage.setItem('cart', JSON.stringify(cart)); console.log('Add to cart:', cart); const totalQuantity = cart.reduce((total, i) => total + i.quantity, 0); setTotalProduct(totalQuantity); } } ``` 有用到local storage的功能: ``` function addToCart() { if (stockLeft > 0 && amount > 0) { localStorage.setItem( new Date(), JSON.stringify({ id: details.id, qty: amount, colorCode: selectColor, size: selectSize, newStock: stockLeft - amount, }) ); updateStock(selectColor, selectSize, stockLeft - amount); setAmount(0); setSelectSize(null); setSelectColor(null); setColorStock({}); } } const updateStockViaLocal = () => { if (details === undefined) { return; } for (let i = 0; i < localStorage.length; i++) { const { colorCode, size, newStock } = JSON.parse( localStorage.getItem(localStorage.key(i)) ); updateStock(colorCode, size, newStock); } }; function calcCart() { let sum = 0; for (let i = 0; i < localStorage.length; i++) { const { qty } = JSON.parse(localStorage.getItem(localStorage.key(i))); sum += qty; } return sum; } function showCartAmount() { let sum = 0; for (let i = 0; i < localStorage.length; i++) { const { qty } = JSON.parse(localStorage.getItem(localStorage.key(i))); sum += qty; } cartCount.forEach((cart) => (cart.textContent = sum)); } app.js ``` ### 我想在子組件A處理資料傳給父組件,再從父組件傳給子組件B使用,分別要做哪些事? 1. 在子組件A中,定義一個函數來處理資料,這個函數將被調用時將資料作為參數傳遞給父組件。 2. 在子組件A中,使用props將剛才定義的函數傳遞給父組件。 3. 在父組件中,定義一個函數來接收子組件A傳遞過來的資料,並將資料存儲在父組件的狀態中。 4. 在父組件中,使用props將狀態中的資料傳遞給子組件B。 5. 在子組件B中,使用props接收來自父組件的資料,並使用它進行相關操作。 ``` //子組件A: function ChildA(props) { const handleData = (data) => { props.onDataReceived(data); }; return ( <div> <button onClick={() => handleData("example data")}>Send Data</button> </div> ); } ``` ``` //父組件: function Parent() { const [data, setData] = useState(null); const onDataReceived = (data) => { setData(data); }; return ( <div> <ChildA onDataReceived={onDataReceived} /> <ChildB data={data} /> </div> ); } ``` ``` //子組件B: function ChildB(props) { return ( <div> <p>Received data: {props.data}</p> </div> ); } ``` 在這個例子中,當在子組件A中點擊"Send Data"按鈕時,將會調用handleData函數並將"example data"作為參數傳遞給父組件的onDataReceived函數,父組件接收到資料後將其存儲在狀態中。最後,子組件B使用props接收來自父組件的資料並將其渲染在頁面上。 * **狀況**: 子組件eslint會報錯,要安裝[PropTypes](https://blog.logrocket.com/validate-react-props-proptypes/),並在component檔裡設定來validat props才能解決 報錯: ![](https://i.imgur.com/WRtmDfw.png) ``` //子組件檔案 import PropTypes from 'prop-types'; function ReactComponent(props) { // ...implement render logic here } ReactComponent.propTypes = { // ...prop type definitions here } ``` ### 用`prevCart.id = 123`更新state的值,state為何會變123? ``` const [cart, setCart] = useState({}); setCart(prevCart => {prevCart.id = 123 return prevCart}); 後面那個object才會被return覆蓋前值,不然會直接return 123 ``` ### 如果state是一個object,setState若只是更新同個object內的值,react會判斷state沒有更新,必須return一個新的object覆蓋舊的(reference的問題) ``` const [cart, setCart] = useState({ //state是一個object id: undefined, name: undefined, price: undefined, color: { code: undefined, name: undefined }, size: undefined, qty: 0, }); // 在修改 cart 对象时,使用扩展运算符来创建新的object(不同的reference) const updateCart = (newCartValue) => { setCart({ ...cart, ...newCartValue }); }; useEffect(() => { const date = new Date(); localStorage.setItem(date.toJSON(), JSON.stringify(cart)); props.setCartQty(updateCartQty()); console.log("cart!"); }, [cart]); ``` ### 在網頁載入,並觸發setDetails改變details之後執行此function,但又不能造成無限循環,有什麼方法? 在 useEffect hook 中設置一個 flag,表示是否要執行 `updateStockViaLocal`。只有在這個 flag 為 true 時,才執行 `updateStockViaLocal`。這樣就可以防止無限迴圈了。 ``` const [details, setDetails] = useState(undefined); const [shouldUpdateStockViaLocal, setShouldUpdateStockViaLocal] = useState(false); useEffect(() => { async function loadData() { try { const res = await fetch( `https://api.appworks-school.tw/api/1.0/products/details?id=${id}` ); const json = await res.json(); setDetails(json.data); setShouldUpdateStockViaLocal(true); // 設置 flag 為 true } catch (error) { console.log(error); } } loadData(); }, []); useEffect(() => { if (shouldUpdateStockViaLocal) { // 只有當 flag 為 true 時,才執行 updateStockViaLocal updateStockViaLocal(); setShouldUpdateStockViaLocal(false); // 執行完畢後,重設 flag 為 false } }, [details]); ``` ``` const cartCount = document.querySelectorAll(".cart-count"); function showCartAmount(){ let sum = 0; for(let i=0;i<localStorage.length;i++){ const { qty } = JSON.parse(localStorage.getItem(localStorage.key(i))); sum += qty; } cartCount.forEach(cart => cart.textContent = sum); } ``` ### React vs Vanilla [命令式/宣告式](https://medium.com/@chih.hsi.chen/imperative-programming-vs-declarative-programming-%E8%BC%95%E9%AC%86%E8%AE%80%E6%87%82%E5%91%BD%E4%BB%A4%E5%BC%8F%E8%88%87%E5%AE%A3%E5%91%8A%E5%BC%8F%E5%87%BD%E5%BC%8F-75a1ea80b6d8) [YUMY React Hook筆記](https://hackmd.io/@yiting444/HJFvrFc3j) | [8種常用的hook](https://youtu.be/VzNNjNmbXpY) ### useState細節 * 想觀察state的變化,console.log(state)要放在render的階段 * setState在render前還不會更新state: ``` const [state,setState] = useState(0); function add(){ setState(state + 1); //re-render前state不會變,所以這行無效 setState(state + 2); } function add(){ setState(prev = prev + 1); //但這樣可以 setState(prev = prev + 2); } ``` * Too many re-render錯誤(通常是setState造成的無限循環) ## useReducer 1.先檢查state跟畫面的關係對不對 2.寫reducer邏輯 3.一個一個dispatch檢查 ``` dispatch({ type:'PRESS_CHARACTER', //命名的藝術 payload:{ value: e.key.toUpperCase(); } }) ```