# React 筆記 熱門的三大前端框架 React、Vue、Angular - React 是一個前端框架,是facebook推出使用的,會應用在比較複雜的專案上。 - Vue 是中國開發的前端框架,通常應用在比較小型的專案上面。 - Angular 是Google開發的前端框架。 - (新)Svelte js > [npm / Yarn 套件管理器指令比對文章 ](https://titangene.github.io/article/npm-and-yarn-cheatsheet.html) > [環境套件設定步驟](https://github.com/eyesofkids/mfee17/tree/main/%E6%95%99%E6%9D%90/0810/React%E5%B0%88%E6%A1%88%E8%A8%AD%E5%AE%9A/CRA-eslint-prettier) - yarn 套件管理基本用法 ```bash= # 下載yarn套件管理工具 $ npm install --global yarn # 建立專案 $ npx create-react-app {檔案夾名稱} # 進到剛剛建立的專案中 $ cd {檔案夾名稱} # script # 執行啟動專案指令 $ yarn start $ yarn build $ yarn test $ yarn eject # 創.env 檔案 內容修改成PORT=3333 ``` - React.Fragment ```bash= # React.Fragment 要包裹著JSX語法 render() { return ( <React.Fragment> <...> </React.Fragment> ); } # <React.Fragment>可用簡寫語法<>來宣告 fragment <></> ``` - 函式型元件 function component - 鉤子都適用use開頭 ex `useState(0)` ```bash= // 函式型元件有鉤子 function App() { const [total, setTotal] = useState(0) return ( <> <h1 onClick={() => { setTotal(total + 1) // 只能用setTotal 改變 total }} > {total} </h1> </> ) } export default App ``` - 類別型元件 class component ```bash= // 類別型元件有生命週期 class App extends React.Component{ constructor(){ super() this.state = { total: 0, } } render(){ return ( <> <h1 onClick={()=>{ this.setState({total: this.state.total + 1 }) }} > {this.state.total} </h1> </> ) } } export default App; ``` ![](https://i.imgur.com/HhqCk4g.png) --- js ES6語法 變數用let 常數用const,傳值後不能再次宣告,傳址可以。 在React中大多還是用const宣告 函式函式表達式只能用const 優先使用const宣告 - hoisting 補充 [我知道你懂 hoisting,可是你了解到多深? ](https://blog.techbridge.cc/2018/11/10/javascript-hoisting/) ```bash= Number.MAX_VALUE Number.MIN_VALUE Math.PI Math.round(4.7) '123'.length 'abcde'.slice(0,2) ``` ```bash= # 函式表達式 const foo = function(x) { return x + 1 } # 箭頭函示 const foo = (x) => x + 1 ``` JSX語法裡搭配使用箭頭函式,因為標記有太多列時,可以使用圓括號(())作 為撰寫時的分行語法 ```bash= const HelloWorld = (props) => ( <div> <h1>{props.text}</h1> </div> ) ``` ![](https://i.imgur.com/W9gTzvW.png) 解構賦值 解構賦值 (Destructuring assignment) 語法是一種 JavaScript 運算式,可以把陣列或物件中的資料解開擷取成為獨立變數。 ```bash= let a, b, rest; [a, b] = [10, 20]; console.log(a); // expected output: 10 console.log(b); // expected output: 20 [a, b, ...rest] = [10, 20, 30, 40, 50]; console.log(rest); // expected output: Array [30,40,50] ``` [展開與其餘運算符(Spread Operator & Rest Operator)語法](https://medium.com/%E5%B0%8F%E5%BD%A5%E5%BD%A5%E7%9A%84%E5%89%8D%E7%AB%AF%E4%BA%94%E5%9B%9B%E4%B8%89/%E5%B1%95%E9%96%8B%E9%81%8B%E7%AE%97%E7%AC%A6-spread-operator-%E8%88%87%E5%85%B6%E9%A4%98%E9%81%8B%E7%AE%97%E7%AC%A6-rest-operator-a97c4fbc6487) 類別(Class)語法 ```bash= class MyClass extends Parent{ constructor() { super() // 繼承父母元件Parent } } ``` 模組(Module)語法 ```bash= # 模組匯入的檔案 import MyClass from './MyClass' # 模組匯出 export default MyClass ``` 也可以有部分匯出 ```bash= # {useState}是hook勾子,也是部分匯入的寫法 import React, { useState } from 'react'; ``` ## JSX 語法 一種針對 JavaScript 語言語法的擴充,需要透過babel編譯過才能在JS引擎中執行。 - 一個render方法中只能有一個根元素(父母元素)回傳 - 一般用<></>來包裹起來,也可以用`<div></div>`元素包起來,但因為是bolck元素有可能會影響到css - 自我封閉 HTML 元素標記一定需要最後結尾 封閉標籤(`/>`) - 英文大寫開頭的標記為自訂元件標記 `<MyButton/>` - 英文小寫開頭的標記為HTML使用的標記 `<h1></h1>` - 元件的加入事件為開頭為 on 的屬性 值必定為一個函式 `<h2 onClick={()=>{}}>` - JS關鍵字的屬性需要改名稱,最常見的是這兩個 - class -> className `<h1 className="">` - for -> htmlFor `<label htmlFor="">` - Style屬性為物件值 建議少用"內聯 style 屬性",改為 "CSS 類別指定" - 內聯 style 屬性 語法要用"小駝峰寫法"還有"{{}}"" `{{ fontSize: 20,backgroundColor:'#fff'}}` - CSS 類別指定 import匯入css檔案 就用css語法寫className="title" - 使用花括號({ })來嵌入JS變數或表達式 - 在JSX語法中要嵌入陣列 經常使用 map 方法 - 列表項目標記,要使用key屬性 - JSX{}中可以放JS表達式 不能放條件判斷式 若結果是 undefind true false 時JSX不會顯示 - if判斷的兩個寫法 ```bash= # &&邏輯運算子 如果 total > 10 用 && 連結要顯示的部分 {total > 10 && <h2>已經大於10</h2>} # if...else 流程控制的表達式語法 (使用三元表達式`? :`) # {條件式 ? 當條件是為真顯示的元素 : 當條件是為否顯示的元素} var beverage = (age >= 21) ? "Beer" : "Juice"; ``` JSX中 陣列可以直接放 物件不能直接放 ![](https://i.imgur.com/dE7UUcV.png) ### 狀態 - 狀態為元件內部私有資料,其它元件無法直接存取到 - setState(屬性) 是唯一能更動 State(狀態) 的方法 - useState 函式型元件勾子(hooks) 類別型元件是用合併的方式 setState 函式型元件是用覆蓋的方式 ![](https://i.imgur.com/gBZhL9u.png) ==注意 setState 是非同步執行!== *async await 對React沒有用 只能用鉤子* - 對應策略 - 策略一:先用變數接住最後更動的值 (推薦!) ```bash= const total = A +1 ; setTotal(total); ``` - 策略二:componentDidUpdate生命周期方法 - 策略三:使用setState第二個傳入參數值(為 callback函式),只有類別型元件可以這樣用 - 單向資料流 只有父母元件可以傳資料給子女元件 自訂元件開頭一定要大寫 一隻程式裡寫一個元件就好 component檔名=function名稱=最後匯出的名稱 --- #### 其他補充 目前的命名方式: - 小駝峰寫法 - 大駝峰寫法 - 全小寫寫法 - 全大寫寫法 .map() 特色 會透過函式內所回傳的值組合成一個新的陣列 並不會改變原陣列 看到map()要加key --- - function component ```bash= const DemoComponent = function() { return ( <div> <h1>Hello React!</h1> <div/> ); }; ``` - ES6class component ```bash= class MyComponent extends React.Component { constructor(props) { super(props); } render() { // Change code below this line return( <div> <h1>Hello React!</h1> </div> ); // Change code above this line } }; ``` - 用props傳值 `{this.props.value}` -> 父母元件對子女元件的溝通方式 ```bash= # Board在Square標籤中帶入value Square用{this.props.value}取得值 class Board extends React.Component { renderSquare(i) { return <Square value={i} />; } } class Square extends React.Component { render() { return ( <button className="square"> {this.props.value} </button> ); } } ``` ```bash= #父母元件傳資料(text屬性)給子女元件,子女元件用props接住物件 function Parents(){ return( <> <Child text="小明" /> </> ) } function Child(props){ return( <> <h1>{props.text}</h1> </> ) } ``` - 綁動態事件範例 `onClick={()=>{}}` ```bash= # 在標籤上綁click事件 <button onClick={()=>{ alert('click'); }}> ``` - 儲存物件狀態 `constructor & setState & state` ```bash= class Square extends React.Component { constructor(props){ super(props); this.state={ value:null, }; } render() { return ( <button className="square" onClick={()=>{ this.setState({value:'X'}) }}> {this.state.value} </button> ); } } ``` - Hook用法 只在函式元件出現 - total 是狀態 - setTotal 是調整狀態的設定 - useState(0) 預設值為0 ```bash= const [total,setTotal] = useState(0); return( <> <button onClick={() => setCount(count + 1)}> Click me </button> </> ) ``` - 避免在style中使用css 盡量用className命名引入css語法 - 如果style要用 `{{小駝峰寫法}}` ```bash= # 接props的時候先解構 就可以直接使用函式 const { handleClick, value } = props; # 經過const { handleClick, value } = props; 解構後可以直接使用函式 handleClick(value); # 未經解構 就接住props的屬性函式來使用 # props.handleClick(props.value); ``` --- index.js -> 引入 App.js -> 引入其他component - 設定預設值 `Child.defaultProps={}` ```bash= # 可以在子元件設定預設值 Child.defaultProps = { text: '請設定預設文字及顏色(預設顏色為粉色)', myColor: 'pink', }; ``` - 限制傳入的屬性類型 `Child.propTypes = {}` ```bash= # 須先安裝套件 $ yarn add prop-types # 可以在子元件設定限制屬性類型 # 注意PropTypes大小寫(=.=) import PropTypes from 'prop-types'; Child.propTypes = { text: PropTypes.string.isRequired, myColor: PropTypes.string.isRequired, count: PropTypes.number.isRequired, }; ``` ![](https://i.imgur.com/iJxBF60.png) - 子女元件傳給父母元件 ```bash= # 父母元件資料 const [pData2, setPData2] = useState('父母元件資料'); # return 父母先將setPData2 傳給子女元件 <ChildB setPData2={setPData2} /> ---------------------------------------------------- # 子女元件資料 const [cbData, setCbData] = useState('子女B資料'); # return 子女用button綁callback函式 # 用父母的setPData2將自己的cbData傳回給父母元件 <button onClick={() => { props.setPData2(cbData); }} > 送資料給父母元件 </button> ``` - 子女元件B傳資料給子女元件A ```bash= # 父母元件把 setPData 帶給子女B const [pData, setPData] = useState(''); # 父母接到B子女的 cbData狀態再用pData 傳給A子女 <ChildA pData={pData} /> <hr /> <ChildB setPData={setPData} /> ------------------------------------------------------ # 子女元件B接住setPData 並用cb傳回cbData狀態給父母 const [cbData, setCbData] = useState('子女B資料'); <button onClick={() => { props.setPData(cbData); }} > 送資料給ChildA元件 </button> ``` --- 8/13 建議先切版再轉成react(注意JSX格式) *編輯>>尋找 可以一次取代* ```bash= # 舉例css轉JSX class -> className style="padding-left" -> style={{paddingLeft}} <img /> -> <img alt="" /> ``` - 釐清狀態 - 盡量都從父元件設定資料 傳給子元件 - 重複元件中的資料格式用json格式 ```bash= # 產品資料變json格式 const products = [{ name:"", img:"", price:300 }, { name:"", img:"", price:300}, { name:"", img:"", price:300 }] # 設定控制全部產品各別數量的狀態 格式結果變成 [1,1,1] # 抓出產品的長度並陣列化 在用.fill() 填入1 const [count ,setCount ] = useState(Array(products.length).fill(1)) ``` *`fill()` 方法會將陣列中索引的第一個到最後一個的每個位置全部填入一個靜態的值。* *`Number.toLocaleString()`該方法返回一個字符串,該字符串具有此數字的語言敏感表示。(具有千位符)* - 用for迴圈修改總數函示 ```bash= # 用for迴圈跑count[index] 來逐一將各產品數量的值相加總=總數量 const productCount =()=>{ let totalCount = 0, for(let i = 0; i<count.length ; i++){ totalCount+=count[i]; } return totalCount; } ``` - 用.map 再把products展開變成清單列表 ```bash= {products.map((product,i)=>{ return( <ProductItem count={count}...../> ) })} ``` - 不能直接更改狀態,要複製一個新陣列 再改值 ```bash= {products.map((product,index)=>{ return( <ProductItem count={count[index]}, # 因為原本count是一個陣列狀態 代表各產品預設數量[1,1,1] # 現在要修改 setCount={(newCount)=>{ const newCount=[...count] newCount[index] = newCount setCount(newCount) }} /> ) })} ``` ==!因為原本count是一個陣列狀態,代表各產品預設數量[1,1,1],但只有setCount能夠修改count,所以要更改count的話要做三步驟!== - 三步驟: 1. 複製一個新陣列 `const newCounts = [...counts];` 2. 修改新陣列 `newCounts[index] = newCount < 1 ? 1 : newCount;` 3. 更新回狀態 `setCounts(newCounts);` --- ### 表單元件 加key值`key={}`讓react區分 最佳化效能 - 看到.map()時 要加key - 列表清單時 要加key [ES6展開運算子](https://pjchender.blogspot.com/2017/01/es6-spread-operatorrest-operator.html) 不同於html 可控表單元件的用法: ![](https://i.imgur.com/2TFpKiU.png) - date `type="date" value={date} onChange={(e)=>{setDate(e.target.value) }}` - 出來格式`"2021-08-21"` - textarea`<textarea value="string"></textarea>` - select`<select value="option value"></select>` 表單元素必要2個條件: 1. value對應到`狀態(data)` 2. 事件callback對應到`設定狀態(setData)` ```bash= # 可控表單元件規則 # 先定義預設值 const [textArea,setTextArea] = useState(""); # 在value中綁定狀態textArea # 中間帶入參數e # 在事件中綁定設定setTextArea(e.target.value) <h1>文字區域</h1> <textarea value={textArea} onChange={(e) => { setTextArea(e.target.value); }} /> ``` - 複選checkbox ```bash= const { value, checkedValueList, setCheckedValueList } = props; const handleChange = (e) => { const value = e.target.value; # 如果目前在這狀態陣列 -> 移出 if (checkedValueList.includes(value)) { # 1. 先從狀態陣列拷貝出新陣列 # 2. 在拷貝的新陣列上處理 # 篩選回傳出"不等於所選的值" # [蘋果,西瓜]->包含西瓜->filter過濾出不等於西瓜的值並回傳->回傳蘋果->(移除西瓜成功!) const newLikeList = checkedValueList.filter((v, i) => { return v !== value; }); # 3. 設定回狀態陣列 return setCheckedValueList(newLikeList); } # 如果沒在這狀態陣列中 -> 加入 setCheckedValueList([...checkedValueList, value]); }; ``` *`.includes('')`查看是否包含其中物件,並回傳布林值* *`.filter('')`會建立一個經指定之函式運算後,由原陣列中通過該函式檢驗之元素所構成的新陣列。* - useRef() 整合第三方實體DOM物件的應用(實體DOM元素的實體參照物件) ```bash= const inputEl = useRef(null) ``` - 表單驗證 (!記得傳送狀態 !記得表單內容要加Name值) ```bash= # 宣告表單資料狀態 # 建議預設值也設好空的資料格式 const [fields,setFields] = useState({ username: '', email: '', password: '', }) # 一樣 fields格式是物件或陣列 要改要三步驟(1複製新狀態、2新狀態上更改、3設定回狀態) # 處理表單 設定狀態 的函式 const handleFieldChange = (e) => { const updatedFields = { ...fields, # 函式型元件記得要自行合併物件值,不然其他物件屬性會被丟棄 [e.target.name]: e.target.value, }; setFields(updateFields); }; # 寫驗證函式 const handleSubmit=(e)=>{ # 先阻止formsubmit的預設事件 e.preventDefault(); } # 自訂表單送出驗證檢查 <form onSubmit={handleSubmit}> </form> ``` [Computed property names ES6計算屬性名稱](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Object_initializer#computed_property_names) ```bash= # Computed property names ES6計算屬性名稱 [e.target.name]: e.target.value, ``` *合併 -> 只會改動相關屬性的值,不影響其他屬性,不會覆蓋。* `.validationMessage` 該消息描述了控件不滿足的驗證約束 --- React 生命週期 -> 抓資料庫會用到 - 從==伺服器要資料==後就要在生命週期中寫更新資料的函式,因為算是第二次render,第一次是自己render元件。 - 通常用在==介接其他第三方應用程式==的時候會用到。 - setState在didUpdate階段只能在判斷式下使用,否則容易便無窮循環 有三階段: 1. 掛載階段 2. 更新階段 - new props - setState() *只有這兩種情況會重新宣染(其他是進階的)* - forceUpdate 強制更新 3. 卸載階段 ![](https://i.imgur.com/uA16bMD.png) ![](https://i.imgur.com/vdAQqZM.png) ### 1. 掛載階段 (類別型元件) ![](https://i.imgur.com/DM8dWaB.png) ### 2. 更新階段 (類別型元件) ![](https://i.imgur.com/8WamtrM.png) ### 3. 卸載階段 (類別型元件) ![](https://i.imgur.com/madnPd6.png) > 卸載要在父元件設定`const [isAlive, setIsAlive] = useState(true);` ### 4. useEffect 用法 (函式型元件) *模仿類別型元件的生命週期* ![](https://i.imgur.com/YvQTcVL.png) --- # CC ![](https://i.imgur.com/f1lqzFu.png) # FC ![](https://i.imgur.com/dxCPFU5.png) - componentDidUpdate可接的三個屬性 componentDidUpdate(prevProps, prevState, snapshot) *snapshot -> 做測試用的* --- ### 電商頁面實例 - 釐清資料狀態 *狀態>>會依據使用者操作而改變的值(被連動的不算)* 頁面上共有5個狀態+1個總體資料狀態=共6個狀態 ![](https://i.imgur.com/yfoOlPW.png) 設定狀態 `const [data,setProduct] = useState('')` 1. 右下過濾後的狀態 `[displayProducts, setDisplayProducts]` 2. 標籤資料狀態 `[tags, setTags]` 3. 價格資料狀態 `[priceRange, setPriceRange]` 4. 搜尋欄狀態 `[searchWord, setSearchWord]` 5. 排序欄狀態 `[sortBy, setSortBy]` 6. 背後的總體資料狀態 `[products, setProducts]` - 寫useEffect 模擬DOM更新時的動作componentDidUpdate -> 處理搜尋 >> 排序 >> 標記 >> 價格 (前面做完資料處理後往下丟) ```javascript= // 當四個過濾表單元素有更動時 // 設定一個空陣列newProducts 把前面篩選做完的資料往下丟 // componentDidUpdate useEffect(() => { let newProducts = []; // 處理搜尋 newProducts = handleSearch(products, searchWord); // 處理排序 newProducts = handleSort(newProducts, sortBy); // 處理勾選標記 newProducts = handleTags(newProducts, tags); // 處理價格區間選項 newProducts = handlePriceRange(newProducts, priceRange); setDisplayProducts(newProducts); // 1秒後關閉指示器 setTimeout(() => { setIsLoading(false); }, 800); }, [searchWord, products, sortBy, tags, priceRange]); ``` - 設定標籤陣列 - 釐清4個功能需要帶入函式的資料 1. 標籤功能 (products, tags) 2. 價格功能 (products, priceRange) 3. 搜尋欄功能 (products, searchWord) 4. 排序欄功能 (products, sortBy) - 寫4個功能函式 > `.filter()` js陣列過濾 > `.sort()` 方法會原地(in place)對一個陣列的所有元素進行排序,並回傳此陣列。預設的排序順序是根據字串的 Unicode 編碼位置(code points)而定。 > [.sort()用法](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) > `find()` 方法會回傳第一個滿足所提供之測試函式的元素值。否則回傳 undefined > `.split()` 將字串用()內的值為標準,分割成陣列 ```bash= const str = 'The quick brown fox jumps over the lazy dog.'; const words = str.split(' '); # 出來是用空格分割的陣列 >> Array ["The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog."] ``` ### refs (類似選取器) React提供的 真實DOM元素實體參照物件 ```bash= const buttonRef = useRef(); # 使用refs $(buttonRef.current).on('click', () => { alert('hello-refs'); }); <button ref={buttonRef}>Click Me(refs)</button> ------------------------------------------------ # 使用id $('#one').on('click', () => { alert('hello-id-jquery'); }); <button id="one">Click Me(id+jquery)</button> ``` - 要寫在 ==useEffect 模擬didMount== 的階段! - didMount階段 才確保DOM元素已經出現了 - 宣告名稱要相同 `ref={buttonRef}`、`const buttonRef = useRef();` - 要加上current 才能抓到真實DOM元件`$(buttonRef.current)` *單向資料流、雙向資料流* ```bash= # 引入jquery import $ from 'jquery'; ``` 寫事件寫在物件上,或是useEffect上(因為要等DOM先生成後才能控制物件) ### 那id跟refs差別? - id概念是"唯一值" 無法重複利用元件 - 複製兩個元件,用id綁定會只有第一個被重複綁定,並且第二個元件失效 - refs 不是唯一值 可以重複使用不影響其他元件 - 複製兩個元件,用Refs綁定不會被重複綁定,並且第二個元件不會失效 --- ### 導入bootstrap樣式 - [導入bootstrap樣式 note](https://github.com/eyesofkids/mfee17/blob/main/%E6%95%99%E6%9D%90/0819/react-bs4-sass%E6%95%B4%E5%90%88/cra-scss-bootstrap4.md) ```bash= # 安裝node-sass $ yarn add sass # 安裝bootstrap@4.6.0 $ yarn add bootstrap@4.6.0 ``` - 在index.js中引入index.scss `import './index.scss';` - 在全站使用樣式global樣式要 放到src資料夾中 ==(index.scss)== ```bash= # index.scss中再加入其他個別樣式 @import './styles/custom.scss'; # 全站都會使用的共同樣式 html { font-size: 12px; } body { margin: 0; padding: 0; font-family: sans-serif; } ``` - 各別使用自訂樣式(覆蓋預設變數) 放到style資料夾中 ==(custom.scss)== ```bash= # 變數覆蓋需要在導入Bootstrap的Sass檔案之前 # 參考Bootstrap的 _variables.scss $primary: rgb(148, 36, 240); $secondary: rgba(10, 10, 10, 0.842); # 導入Bootstrap所有的樣式與預設變數 @import '~bootstrap/scss/bootstrap.scss'; # 其它要導入覆蓋掉原本的預設Bootstrap樣式要放在這下面 .alert-primary { color: #020c16; background-color: #5e94ce; border-color: #b8daff; } ``` - 使用 React Icons https://react-icons.github.io/react-icons/ ```bash= # 引入 import { ImFilm } from 'react-icons/im'; #在jsx中當元件使用 <ImFilm /> ``` --- 0820 上午 ### Router ```bash= # 安裝 react-router-dom 模組 $ yarn add react-router-dom ``` - 最外層元件必須是Router元件 - Router元件直接由React Router模組導入,一般都是使用BrowserRouter作為Router元件 - Switch元件通常會包裹Route元件 - 以Link取代a > 使用a與href有可能會導致頁面刷新,元件會重新回恢初始狀態,導致應用程式的運作失常,所以請儘可能用Link元件 - Route 網址長的往上放 - Home 放在最後一個 並加上exact ```bash= # 引入router 一般都是使用BrowserRouter作為Router元件 import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'; function App() { return ( # <Router>包在最外層 <Router> <> <h2>Link連結</h2> # link取代a連結 <Link to="/">Home</Link> <Link to="/about">About</Link> # 用<Switch>包裹<Route> # 網址長的往上放 <Switch> <Route path="/about"> <About auth={auth} /> </Route> # exact 用在首頁 -> exact等同於exact={true} <Route exact path="/"> <Home auth={auth} /> </Route> </Switch> </> </Router> ); } ``` - Link中to屬性可以使用物件的定義方式,來定義這個連結路徑的參數值、hash值、state值,例如以下的範例: ```bash= <Link to={{ pathname: "/courses", search: "?sort=name", hash: "#the-hash", state: { fromDashboard: true } }} /> ``` ### React Router三個重要的屬性值 使用React Router後會綁入使用的元件的props,以下三個相關的屬性值:match、history、location這三個屬性 - withRouter 是 HOC高階元件 - 要使用withRouter才能讓元件擴充獲得上面三個屬性match、history、location ```bash= import { withRouter } from 'react-router-dom' ``` ```bash= import React, { useState, useEffect } from 'react' # 都需引入withRouter擴充 import { Link, Switch, withRouter } from 'react-router-dom' # 因為使用withRouter所以擴充在"props"元件上面 可以獲得match、history、location這三個屬性 function Product(props) { console.log(props) return ( <> <h1>Product</h1> <h3>{props.match.params.id}</h3> </> ) } # export時要使用withRouter export default withRouter(Product) ``` ## 1. history: 對於瀏覽器書籤、前後移動的處理方法 -> 瀏覽器歷史紀錄 - push:跳到某個頁面 - goback:回到上一頁 - ### history.push(path,[state]) 連到其他頁面 - [state]參數可使用代表他是從哪裡跳來的 - (使用Link也可以達到同樣功能,差別在程式邏輯) 要使用`props.history.push('/about')` ```bash= <button onClick={() => { # 用push連到其他頁面 props.history.push('/about') }} > 連到 關於我們 </button> ``` - ### history.goBack 回到上一頁面 ```bash= <button onClick={() => { # 用goBack連到其他頁面 props.history.goBack() }} > 回到上一頁 </button> ``` ## 2. match: 主要要得到不同路徑的參數值 - (要使用巢狀路由就會使用到match的path&url這兩個屬性,會比較複雜,建議小型專案只用一個Router,最好都放在同一頁) - path:目前路由 - url:目前路由 - params:網址上的參數 `網誌url/product/1111` - ### match.params 參數設定如下 ```bash= # ?代表id可有可無 <Route path="/product/:id?"> <Product auth={auth} /> </Route> ``` ```bash= # 用match.params 使用網址上的id和伺服器要資料 const id = props.match.params.id ``` ```javascript= import React, { useEffect, useState } from 'react' import { withRouter } from 'react-router-dom' // 模擬從伺服器來的資料 const products = [ { id: '1', picture: 'http://placehold.it/32x32', stock: 5, name: 'iPhone 12 Pro', price: 25000, tags: '蘋果,大螢幕', }, { id: '2', picture: 'http://placehold.it/32x32', stock: 5, name: 'iPhone 12', price: 15000, tags: '蘋果,小螢幕', } ] function Product(props) { console.log(props) // 如果抓出來資料都是物件的話 建議還是給預設的初始化物件格式 const [product, setProduct] = useState({ id: '', picture: '', stock: 0, name: '', price: 0, tags: '', }) // didMount 要有狀態才能吃伺服器來的資料 useEffect(() => { //用match.params 使用網址上的id和伺服器要資料 const id = props.match.params.id const newProduct = products.find((v) => { return v.id === id }) if (newProduct) setProduct(newProduct) }, []) return ( <> <h1>Product</h1> <p> {product.name}/{product.price} </p> </> ) } export default withRouter(Product) ``` --- 0820下午 - 部分導出就要用部分導入 ```bash= # 部分導入 import { data } from './pages/data'; # data檔案 # 模擬從伺服器來的資料 export const data = [ { id: '1', picture: 'http://placehold.it/32x32', stock: 5, name: 'iPhone 12 Pro', price: 25000, tags: '蘋果,大螢幕', }, { id: '2', picture: 'http://placehold.it/32x32', stock: 5, name: 'iPhone 12', price: 15000, tags: '蘋果,小螢幕', }, ..... { id: '5', picture: 'http://placehold.it/32x32', stock: 6, name: 'Google Pixel 5', price: 17000, tags: '安卓,大螢幕', }, ] ``` ```bash= # 特別提醒如果抓出來資料都是物件的話 建議還是給預設的初始化物件格式 const [product, setProduct] = useState({ id: '', picture: '', stock: 0, name: '', price: 0, tags: '', }) # 要有狀態useEffect&useState才能吃伺服器來的資料 ``` - 可以在Link中帶入參數 ```bash= <Link to={'/recommend/detail/' + detail.id}> <button className="btn btn-primary">查看更多</button> </Link> ``` ## 3. location: 目前所在的位置(物件值) - pathname:目前路由 - state:從哪個頁面來 - search:網址上的`?test=123` - ### location.search 使用網址上的id和伺服器要資料`?id="1"` - 類似於 match.params - [文件URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) ```bash= # 使用網址上的id和伺服器要資料 # 使用.location.search const paramsString = props.location.search # 文件上固定用法 const searchParams = new URLSearchParams(paramsString) # 記得網址上id是用get去抓 const id = searchParams.get('id') # 在其他Link上也可以使用 /?id= <Link to={'/product-detail-qs/?id=' + v.id}>{v.name}</Link> ``` ## 4.match.params V.S. location.search(url querystring) 差別 網址帶id可以使用這兩個方法: match.parmas >>過去SEO比較好 url-query string >>過去SEO比較不好 ---- ## 麵包屑 - 1.寫一個麵包屑元件 MultiLevelBreadCrumb.js ```javascript= // MultiLevelBreadCrumb.js import React from 'react' // 高階元件樣式(HOC),增強元件用的 import { withRouter, Link } from 'react-router-dom' // 中文路徑對照陣列,移出到config/index.js中設定 import { pathnameList, pathnameTextList } from './config' function MultiLevelBreadcrumb(props) { const { location } = props // find index,目前匹配的pathname,它的中文是什麼 const findPathnameIndex = (pathname) => { // 找到剛好的,從前面開始找起 const foundIndex = pathnameList.findIndex((v, i) => v === pathname) // 沒找到剛好的路徑時用的(動態id params會遇到) // 例如:product/detail/123 // 會用patchnameList的最後一個開始找起 // 找到最長的比對的那個為路徑 // ex. `product/detail/112` 會找到 `product/detail` if (foundIndex === -1) { for (let i = pathnameList.length - 1; i >= 0; i--) { if (pathname.includes(pathnameList[i])) { return i } } } return foundIndex } // 有一個項目和二個項目的情況 const formatText = (index) => { if (index === -1) return '' // '/產品/嬰兒/初生兒' -> ['','產品','嬰兒', '初生兒'] const textArray = pathnameTextList[index].split('/') // '/product/baby/birth' -> ['','product','baby', 'birth'] const pathArray = pathnameList[index].split('/') console.log(textArray, pathArray) const listArray = textArray.map((v, i, array) => { if (i === 0) return '' if (i === array.length - 1) { return ( <li className="breadcrumb-item active" aria-current="page"> {v} </li> ) } return ( <li className="breadcrumb-item"> <Link to={pathArray.slice(0, i + 1).join('/')}>{v}</Link> </li> ) }) return listArray } return ( <> <nav aria-label="breadcrumb"> <ol className="breadcrumb"> <li className="breadcrumb-item"> <Link to="/">首頁</Link> </li> {formatText(findPathnameIndex(location.pathname))} </ol> </nav> </> ) } export default withRouter(MultiLevelBreadcrumb) ``` - 2.和辨別網址的頁面,長的網址往下寫 ```javascript= export const debug = true // 測試開發/營運網址 export const devUrl = 'http://localhost:3000' export const prodUrl = 'http://www.abc.com' export const pathnameList = [ '/about', '/login', '/product-category', '/product-category/product-detail', '/product-category/product-detail-qs', ] export const pathnameTextList = [ '/關於我們', '/會員登入', '/產品總覽', '/產品總覽/產品詳細', '/產品總覽/產品詳細QS版', ] ``` - 3.在需要使用麵包屑的文件中引入寫好的麵包屑 ```javascript= // 在需要使用麵包屑的文件中引入寫好的麵包屑 import MultiLevelBreadCrumb from './MultiLevelBreadCrumb' return ( <> <h1>ProductCategory</h1> // 使用麵包屑元件 <MultiLevelBreadCrumb /> <h2>使用match.params</h2> .... ``` - React Router使用說明 https://github.com/eyesofkids/mfee17/blob/main/%E6%95%99%E6%9D%90/0820/react-router/react-router%E4%BD%BF%E7%94%A8%E8%AA%AA%E6%98%8E.md - https://github.com/eyesofkids/mfee17/blob/main/%E6%95%99%E6%9D%90/0820/react-router/react-router%E4%BD%BF%E7%94%A8%E8%AA%AA%E6%98%8E.md#react-bootstrap%E5%B0%8E%E8%A6%BD%E9%81%B8%E5%96%AE%E9%A0%85%E7%9B%AE%E9%BB%9E%E4%BA%AEactive%E8%AD%B0%E9%A1%8C - 使用activeClassName="" 也可以自己加active樣式 ```bash= <NavLink to="/faq" activeClassName="selected"> FAQs </NavLink> ``` - 找不到頁面404 not found ```bash= # 404頁面,找不到頁面 Route要放在最下面,path用*接住全部not found <Route path="*"> <NotFoundPage /> </Route> ``` - ScrollToTop:在Linkck換頁的時候不會跳到最上面,要引用ScrollToTop ```bash= # 設定ScrollToTop元件 在ScrollToTop.js import React from 'react'; import { withRouter } from 'react-router-dom'; # 頁面切換時要用捲軸讓頁面回到最上方 class ScrollToTop extends React.Component { componentDidUpdate(prevProps) { if (this.props.location !== prevProps.location) { window.scrollTo(0, 0); } } render() { return this.props.children; } } export default withRouter(ScrollToTop); ``` ```bash= # 再在App.js中設定引入ScrollToTop元件 import ScrollToTop from './components/ScrollToTop'; # 再把要跳到最上面的Route ,用<ScrollToTop><ScrollToTop/>包起來 <ScrollToTop> <Switch> <Route path="/recommend/bear/result"> <Result /> </Route> <Bear /> </Switch> </ScrollToTop> ``` --- 0903 上午 ### 資料庫同步 - 建立一個schema資料夾,放入最新的資料庫語法 除了phpadmin SQL工具外還可使用 HeidiSQL https://ithelp.ithome.com.tw/articles/10193251 --- 0906 上午 ### 購物車紀錄儲存位置 1. localstorage 或 cookie 客戶端 2. session 伺服器端 3. 記在資料庫中 ( 實務上會使用的方法 -> 因為商務上可以觀察統計資料數據幫助行銷 ) - orderstep範例示範 - 螞蟻金服 套件 https://ant.design/index-cn - 匯入圖片要先import - react在jsx中写backgroud-size属性backgroud要用backgroudImage代替才行; ```bash= # img JSX <img className="recommend-cardimg img-fluid" src={`${IMAGE_URL}/img/article-img/${detail.pic}`} alt="..." /> # backgroundimg JSX # react在jsx中写backgroud-size属性backgroud要用backgroudImage代替才行; <div className="recommend-detailBg" style={{ backgroundImage: `url("${IMAGE_URL}/img/article-img/${detail.pic}")`, borderRadius: 10, backgroundSize: 'cover', backgroundPosition: 'center', }} > ``` * [理解React的setState到底是同步還是非同步(上) ](https://ithelp.ithome.com.tw/articles/10257993) * [下篇 - setState是同步/非同步的時機和如何取得更新後的state值](https://ithelp.ithome.com.tw/articles/10257994)