# [第03堂] React基礎知識-教材篇 :::info :bulb: **小複習:** * **Node.js**:開源的 JavaScript 執行環境 * **NVM**:Node 的**版本**管理器 * **NPM**:Node 的**套件**管理器 * **NPX**:一樣是Node 的**套件**管理器,不過用完就會刪掉。 ::: ## ES5/ES6 >ES的全名是ECMAScript,是一個由Ecma International訂定的腳本語言標準化規範,JavaScript就是根據這個規範去訂定的,[傳送門](https://medium.com/%E6%8B%89%E6%8B%89%E7%9A%84%E7%A8%8B%E5%BC%8F%E7%AD%86%E8%A8%98/%E8%A2%AB%E8%80%83%E5%80%92%E7%B3%BB%E5%88%97qq-es5-es6-es7-es8-es9-es10-c3dab7653373) ### ES5 * 嚴格模式 * Array/Object操作的方法 * Array.find * Array.filter * Array.map * ... ### ES6 * **let & const** * let可變動 * const不可變動,宣告時要給定initialize ```javascript== //let let a; a=10; console.log(a); a=20; console.log(a); //const const b=100; console.log(b); //下面可以拿掉註解測試看看 //b=200; //console.log(b); ``` * **箭頭函式 Arrow Functions** ```javascript== //不帶參數 //ES5 function func_name(){ console.log('Hello World!'); } //ES6 func_name = () =>{ console.log('Hello World!'); } //帶一個參數 //ES5 function func_name(e){ console.log(e); } //ES6 func_name = e =>{ console.log(e); } //帶兩個(含)以上參數 //ES5 function func_name(e,i){ console.log(e,i); } //ES6 func_name = (e,i) =>{ console.log(e,i); } //關於return func_name = () => something func_name = () =>{ return something; } ``` * **參數預設 Default Parameters** 傳入function的參數沒有給定值時,會出現Exception,最快的解決方法就是給定預設值,在ES6中給定預設值的方式更為快速簡單 ```javascript== BMI=(height=1.75, weight=65) => { let BMI=weight/(height^2); console.log(BMI); } ``` * **文字模板 Template Literals** ```javascript== // ES5 var name='Jack'; var start = 'Hello'+name+'!'; //ES6 const name='Jack' const start = `Hello ${name}!` ``` * **多行字串輸入 Multi-line Strings** ```javascript== // ES5 var fruit = 'apple,' + 'banana' //ES6 const fruit = `apple, banana` ``` * **解構賦值 Destructuring Assignment** <font color="red">**他是在ES6的新特性**</font> ```javascript== const Jack={age:40,gender:'male'}; //一般 console.log(Jack.age,Jack.gender); //解構 const {age,gender}=Jack; console.log(age,gender); //賦值 const Jack {age:x} = {age:40}; //x=40 ``` * **物件實字 Enhanced Object Literals** 假如object內的元素其key與value名稱一樣,可以省略key直接寫 value 名稱即可。 ```javascript== // ES5 var name = 'Jack'; var obj = { name: name }; // ES6 const name = 'Jack'; const obj = { name }; ``` * **Promises** 非同步專用 ## JSX > **JavaScript XML,JavaScript 的語法擴充,屬性名稱以小駝峰方是撰寫,要比較留意的是最外層只能有一個根元素。** 下面的標籤語法不是一個字串也不是HTML,而是把右邊的HTML當成一個物件存到變數中,這種把HTML寫在JavaScript的程式碼中的技術,我們稱之JSX。 ```javascript=== const element = <h1> 你好,世界!</h1>; ``` 可以在HTML標籤中利用「{}」寫JavaScript表示式。 ```javascript= const showOne=true; const element = <h1>{showOne?1:0}</h1> ``` 或是將style變為一物件、屬性名稱規則改用駝峰法(用大寫區隔)、屬性的值變成字串。 ```javascript= const styleArgument={ fontSize: '100px', color: 'red' }; ReactDOM.render( <h1 style={ styleArgument }> Hello, world! </h1>, document.getElementById('root') ); ``` 在JSX中的HTML標籤屬性基本上都與原本的HTML標籤屬性相同,比較特別的是class和for是js的保留字,因此這兩個屬性在JSX需要寫成**className**和**htmlFor**。 ```javascript= <div className="title">標題</div> ``` 因為JSX 並不是單純的 JS ,所以是沒辦法直接被套用在網頁上的,這時候就必須要透過 babel 進行轉譯並搭配 Webpack 進行打包的動作。 而透過babel進行轉役會變成怎麼樣子呢? ```javascript= <a href="https://facebook.github.io/react/">Hello!</a> ``` 例如上面的Hello world! 透過 babel 編譯後會轉成下面 React library 的 React.createElement() function calls。 ```javascript= // React.createElement(元件/HTML標籤, 元件屬性,以物件表示, 子元件) React.createElement('a', {href: 'https://facebook.github.io/react/'}, 'Hello!') ``` :::info :bulb: 不過 React 並不要求使用 JSX,只是與createElement相比,JSX可閱讀性比較高。 ::: **下面會簡單介紹一些JSX的寫法** * **變數**:用 {} 包起來 ```javascript= const name = 'Andy' const htmlA = <h1>Hello {name}<h1> const htmlB = <h1>`Hello ${name}`<h1> ``` * **多行的 HTML**:最外層多加一個 () ```javascript= const html = ( <div> <span>Hello Andy</span> </div> ) ``` * **if**:改寫成三元運算子 { } 中只能放 JavaScript expression,而if 是一個 JavaScript statement,所以在 JSX 無法使用 if。 ```javascript= const showOne=true; const element = <h1>{showOne?1:0}</h1> ``` * **html標籤中的數字**: 如果值是數字,JSX 會幫你加上 "px" 單位,若不想用 px 就得明確指定單位。 ```javascript= // 值同 10px <div style={{ height: 10 }}> Hello World! </div> ``` :::info :bulb: 小練習 * 寫一個有底色、寬100%、高50px的div (用inline style) * 宣告 a = Hello * 在div中顯示Hello world (Hello必須是變數a) ::: ## Scss > **現今主流的CSS預處理器之一** * **巢狀結構** ```javascript== #banner{ ... #logo{ // 等同於 #banner #logo ... img{ // 等同於 #banner #logo img ... } } nav{ //等同於 #banner nav ... } } ``` * **&連接** ```javascript== a{ color:red; &:hover{ //等同於 a:hover color:red; } &:active{ //等同於 a:active color:blue; } } ``` :::info :bulb: 小練習 * 將css改用引入css檔案的方式撰寫 ![image](https://hackmd.io/_uploads/HJ-nsmdaT.png) ::: ## Components > 我們稱之元件,Component 的字首須為大寫字母 React 提供了兩種元件實做方式,分別為Class & Functional, ### Class Component > 使用 Class(類別) 建立的元件,具有生命週期,具有State,擁有this,需要引入React Component,一定要實作render。 ```javascript= import React, { Component } from "react"; export default class Button extends Component { //用extends去繼承Component constructor(props) { super(props), } const handleButtonClick =()=>{ console.log('click'); } render() { //用render()函式去收集要渲染到畫面上的東西(放在return值),再去改變DOM return <button onClick={this.handleButtonClick}>btn</button>; } } ``` ### Functional Component > 使用 Function(函式) 建立的元件,沒有生命週期,沒有State,編譯更快(因為不用將class轉換成es5) ```javascript= function Button(props) { const handleButtonClick =()=>{ console.log('click'); } return <button onClick={handleButtonClick}>btn</button>; } ``` 不過在Hook出現後Functional Component 也具有生命週期以及State。 :::info :bulb: Hook 在 React 16.8 引入的一種特性,可以在函數式組件中使用 React 功能的 API。以前如果想在元件中使用生命週期、State 等,一定要使用 Class 元件,直到 Hook 出現後,不使用 Class 的情況下也能具有生命週期以及State。 ::: ### Props & State >React 中 Component 只能透過資料狀態的改變來更新 UI,而 React 元件有兩種資料來源: >* 透過外部傳進來元件的 Props >* 元件內部自己維護的 State #### props >properties的縮寫,屬性。 >用於元件與元件間傳遞資料,是靜態(唯讀)的。 #### state >內部狀態 >元件存放資料的地方,以物件的方式存放(key:value),需要在constructor建立自身state,並且利用setState去改變state的值。 ```javascript= // Class寫法 constructor(props) { super(props); // 建立state並給予初始值 this.state = { currentPage: 'one', } } // 利用this.setState 改變state的值 this.setState({ currentPage:'two', }) // Hook寫法 const [page,usePage] = useState("one"); usePage("two"); ``` constructor 是在 Component 建立的時候自動執行的 function,所以如果沒有要做任何事情的時候,可以不用寫 constructor。但如果有這個需求,記得要把寫好寫滿 constructor(props) 、 super(props) 這樣才可以確保東西都有初始化好。 ## this > 在 React 類組件中的 this 通常指向該類的實例,即指向當前元件的實例。 這意味著可以通過 this 訪問該組件的狀態(state)和屬性(props),以及元件的方法,ex:可以使用 this.setState() 來更新組件的狀態,以及訪問 this.props 來獲取父組件傳遞的屬性。 ## setState >可以透過this.setState()修改State。 只需要傳入想要更新的 key-value pairs ,React 會自動將傳入和當前State的 key-value pairs 合併 (merge),把新的 key-value paris 會取代掉舊值。 ```javascript= this.setState({ name: "cherry" }) ``` state 的更新是非同步的,因此執行完 setState() 更新 state 後,接著下一行 code,馬上存取 this.state 不一定會拿到最新的值。 setState 還有一個 optional 的第二個參數 callback,callback 是一個 function,用途是當 state 確定已經被更新後,React 就會 call 這個 callback function,讓你可以在 callback 中做些你需要確定 state 已經被更新後才能做的事。 ```javascript= setState(stateChange, callback) // 舉個例子 changeInput(event) { // 會先將值存到text中,再執行後面的validateInput function this.setState({ text: event.target.value }, function() { this.validateInput(); }); } validateInput() { if (this.state.text.length === 0) { this.setState({ inputError: 'Input cannot be blank' }); } } ``` 而setState 也可以接受一個 function 當作參數,該 function 的參數 prevState 是當前最新的 state,而參數 props 是元件當前的 props,在 function 返回一個你要更新的 state 。 ```javascript= // 錯誤寫法 this.setState({ counter: this.state.counter + this.props.increment, }); // 正確寫法 this.setState((prevState, props) => ({ counter: prevState.counter + props.increment })); ``` ## Router [傳送門](https://ithelp.ithome.com.tw/articles/10276645) ### react-router-dom >react的一個常用套件,可以幫助我們在網頁不刷新的狀況下面做到更新網址,做SPA的時候會需要用到。 >現行版本為v6,和v5有蠻大的不同,開發的時候要注意一下。 ``` ## 安裝指令 $ npm install react-router-dom ``` **BrowserRouter、HashRouter** >以標籤形式包覆SPA最上層的元件,讓元件(包含裡面的子元件)擁有路由的功能 * **BrowserRouter** * 使用HTML5 history API (pushState, replaceState, popState) * BrowserRouter的路徑格式:mypage.com/home * 建立在 HTML history API之上,顯示的url簡潔。 網站上線後,需要有後端配合,接受瀏覽器向這個ur發出的請求,不然會造成連線錯誤。 * **HashRouter** * 使用URL的hash(window.location.hash) * HashRouter的路徑格式:mypage.com/#/home * 使用 hash url ,也是就 # 來控制url,瀏覽器不會對的url作出請求。 --------- * **Switch** (但v6版本已經沒辦法使用了,改成Routes) 放在JSX模板的節點內,Switch標籤裡面可以包多個標籤(只能包Route),並控制如果路徑和兩個Route的path都匹配,Switch只會渲染第一個匹配的的Route。 * **Route** 根據路徑渲染對應的Component * **path**:網址後面的後面的路徑。 * **component**:此route要顯示的元件。 * **exact**:加上exact,url路徑一定要和path完全相同,才會渲染component指定的元件。(例子1) * **sensitive**:url和path的大小寫必須完全相符才會渲染元件。(例子2) * **strict**:搭配exact使用,檢查url的斜線是否也和path完全相同,讓url和path的匹配更嚴謹。 * **Redirect** 重新導向,和Route一樣可以放在Switch標籤之中。 * **to**:必要屬性,指定要重新導向的路徑。(例子3) * **from**:from 屬性只有當 Redirect 在 Switch 標籤中和 to 一起時可以使用,當 url 和 from 相符時,便會轉向 to 指定的路徑。(例子4) * **push**:會將重定向的新路徑 Push 至歷史記錄保留,而不是用Replace取代,回到上一頁沒有紀錄。(例子5) * **另外**:Redirect也和Route一樣有exact、strict、sensitive屬性和from搭配使用,負責檢查url和from的匹配條件。 ```javascript= ## 例子1 <Route exact path="/home" component="Home" /> ## 例子2 <Route sensitive path="/Home" component="Home" /> ## 例子3 <Route path='/home' component={Home} /> <Route path='/1' component={One} /> <Redirect to="/home" /> //url如果沒有找到匹配的path就會將頁面導向'/home' //例如:在url輸入'/333'後,url會自動變成'/home'將你導向渲染Home的頁面 ## 例子4 <Switch> <Redirect from='/3' push to="/home" /> <Route path='/home' component={Home} /> <Route path='/1' component={One} /> <Route path='/2' component={Two} /> </Switch> ## 例子5 ```javascript= <Redirect push from='/3' to="/home" /> ``` * **Link** 類似於HTML的a標籤 * **NavLink** 進階的Link,可以根據現在的路由給定Active Style --- * Provider * Router * RouterApp ```javascript= ## store/index.js import { createStore, applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; import { composeWithDevTools } from 'redux-devtools-extension'; import subscription from "./subscription"; import reducers from '../reducers'; import rootSaga from '../sagas'; const sagaMiddleware = createSagaMiddleware(); const store = createStore( reducers, composeWithDevTools( applyMiddleware(sagaMiddleware), ) ); export default store; sagaMiddleware.run(rootSaga); subscription(store); ``` ### 額外補充 ```javascript= ## index.js ReactDOM.render( <Provider store={store}> <Router history={history}> <RouterApp /> </Router> </Provider>, document.getElementById("root") ); # v2 import React from 'react' import ReactDOM from 'react-dom/client' import './index.css' import App from './App' import { BrowserRouter } from 'react-router-dom' const root = ReactDOM.createRoot(document.getElementById('root')) root.render( <BrowserRouter> <App /> </BrowserRouter> ) ``` ```html= # route.js(App.js) <EmptyLayout> <HashRouter> <Switch> <Route path="/login" component={LoginScreen} /> <Redirect from="/" to="/login" /> </Switch> </HashRouter> </EmptyLayout> # v2 function App() { return ( <div className='App'> <h1>Hello</h1> <Routes> <Route path='/' element={<Layout />}> <Route index element={<Home />} /> <Route path='about' element={<About />} /> <Route path='dashboard' element={<Dashboard />} /> <Route path='*' element={<NoMatch />} /> </Route> </Routes> </div> ) } ``` * <React.StrictMode> <Fragment> https://zh-hant.legacy.reactjs.org/docs/fragments.html 不會渲染任何額外的 DOM 元素。 用於突出顯示應用程式中的潛在問題。 啟用嚴格模式 有時 React 應用程式中的錯誤可能很難追踪,特別是諸如 race conditions 或對元件狀態的不正確假設等細微問題。啟用嚴格模式的額外檢查和警告,您可以在這些錯誤導致嚴重問題之前發現它們。 reportWebVitals.js 為了在單頁應用(SPA)中監控各種重要的網頁效能指標,以便優化性能並提高用戶體驗。