# 從 jQuery 到 React ###### tags: `KK 工具箱` ## 05/30(四)18:30 - 21:30 ### 環境建置 * 推薦使用 [iTerm2](https://www.iterm2.com/) + [oh-my-zsh](https://ohmyz.sh/) * cd * ls * rm * mv * 推薦使用 [VS Code Editor](https://code.visualstudio.com/) * 推薦插件 * prettier * vscode-icons * path intellisense * npm intellisense * code spell checker * 推薦使用 [yarn](https://yarnpkg.com) 套件管理工具 * yarn v.s. npm install * yarn (global) add v.s. npm install {pkg} * yarn remove v.s. npm uninstall {pkg} * yarn upgrade v.s. npm update/upgrade * 推薦使用 create-react-app(CRA) 腳手架 * create-react-app todolist ### jQuery v.s. React * 模組化 * 協作性 * 高效能 ### React 設計理念 Virtual DOM ![](https://i.imgur.com/feTIQyB.png) ### React 專案結構 * 運作原理 ![](https://i.imgur.com/blPkHdk.png) * 專案結構 ``` . ├── public │   ├── favicon.ico │   ├── index.html │   └── manifest.json ├── src │   ├── App.css │   ├── App.js │   ├── App.test.js │   ├── index.css │   ├── index.js │   ├── logo.svg │   └── serviceWorker.js ├── README.md ├── package.json └── yarn.lock ``` > 進階:大型 React App 檔案架構 > 1. Organize by features > 2. Create strict module boundaries > 3. Avoid circular dependencies ``` CSS -> CSS Module / Styled Component src ├── images │ ├── icons │   │ ├── icon-menu.svg │   │ └── icon-user.svg │ ├── brands │   │ ├── logo.svg │   │ └── logo-w.svg │ └── mockups │   ├── mockup-user.png │   └── mockup-banner.png ├── components │ ├── auth │   │ ├── AuthForm.js │   │ └── AuthModal.js │ └── common │   ├── App.js │   ├── Modal.js │ └── buttons │    ├── Button.js │    └── SocialButton.js ├── index.js └── serviceWorker.js ``` ## 06/06(四)18:30 - 21:30 ### 穩住 JS #### callback ```javascript= function callback() { // do something } callback // 紙條 callback() // 打電話 // 常見模式 1: error-first some_func(arg1, arg2, function(error, result) { // do something }) // 常見模式 2: split callback some_func(arg1, arg2, function(result) { // handle success }, function(error) { // handle error }) ``` ```javascript= function add(a, b, callback) { try { var c = a / b; callback(null, c); } catch(error) { callback(error); } } ``` ```javascript= // quiz function foo(name, cb) { console.log(name); cb(null); } function cb(error, result) { console.log(error, result); } foo('KK', cb); ``` #### this ```javascript= // 誰叫我? function foo() { console.log(this) } foo() var bar = { woo: 1, foo: foo } bar.boo() var woo = bar.foo woo() ``` ```javascript= // quiz var foo = { bar: function() { console.log(this) }, woo: { lala: function() { console.log(this) } } } foo.bar() foo.woo.lala() var foowoolala = foo.woo.lala foowoolala() ``` #### ES6 * Default argument ```javascript= const foo = (a, b = 1) => { return a + b } foo(3) ``` * Template Expression ```javascript= const name = "KK" const foo = `Hi, this is ${name}.` const bar = `Hi, they are ${names.map(name => name.toUpperCase()).join(', ')}.` ``` * Object Property Value Shorthand ```javascript= const name = "KK" const foo = { name, value: 123 } ``` * Spread syntax ```javascript= // split const foo = {a: 1, b: 2, c: 3} const bar = [1, 2, 3] const {a, b, ...restFoo} = foo // restFoo = {c: 3} const [a, b, ...restBar] = bar // restBar = [3] // merge console.log({d: 4, e: 5, ...foo}) console.log([4, 5, ...bar]) ``` ```javascript= // quiz const foo = (arg) => { const {a, b, ...rest} = arg console.log(rest) } const foo = ({a, b, ...rest}) => { console.log(rest) } foo({a: 1, b: 2, c: 3, d: 4}) const [, , , id] = url.split('/') // const slices = url.split('/') // const id = slices[3] ``` * import / export ```javascript= import default_module from 'module-name' import { submodule1, submodule2 } from 'module-name' import * as no_defualt_module from 'module-name' import * as AWS from 'aws-sdk' import AWS from 'aws-sdk' const s3 = new AWS.S3() import * as Redux from 'redux' import * as ApolloReact from 'apollo-react' Redux.connect() ApolloReact.connect() ``` ```javascript= export default module export const submodule export { submodule1, submodule2 } ``` ```javascript= // foo.js import { bartender } from './bar' // import * as bar from './bar' -> bar.bartender import woo from './woo' // bar.js export const bartender = [1, 2, 3] // woo.js export default {a: 1, b: 2, c: 3} ``` * let / const ```javascript= // var - function scope // let/const - block scope function() { var a = 1 let b = 2 const c = 3 } console.log(a, b, c) if (true) { var a = 1 let b = 2 const c = 3 } console.log(a, b, c) // const is for the memory addreess const foo = [] foo.push(1) // foo -> [1] foo[0] = 2 ``` * Arrow Function ```javascript= // 不會產生新的上下文 const foo = () => { console.log(this) } const bar = { foo, woo: { foo } } foo() bar.foo() bar.woo.foo() const foo = function() { console.log(this) const bar = () => { console.log(this) const woo = function() { console.log(this) } } } ``` * Class ```javascript= class Person class CookMan extends Person new Person() new CookMan() const kk = { name: "KK", sayHi: () => console.log('Hi') cook: () => console.log('lkk_cook') } const ken = { name: "Ken", sayHi: () => console.log('Hi~') } class ClassName extends SuperClassName { variableName = 1; constructor(args, extra_args) { super(args) } methodA() { console.log(this) } methodB = () => { console.log(this) } methodC = function() { console.log(this) } } const instance = new ClassName("KK") instance.variableName instance.methodA() instance.methodB() instance.methodC() class Dad { money = 10 constructor(age) { this.age = age this.property = 10000000 } taxi = () => { // take taxi } } class Son extends Dad { constructor(property) { super(6) this.property = this.property > property ? this.property: property } } const son = new Son(9999) ``` * Promise > 敬請期待下次 ### 組件概念 ![](https://i.imgur.com/7IxEg7Y.png) ### 組件用法 ```jsx= <Component key="xxx" ref={ref => {}} onEvent={event => {}} style={{ marginTop: "10px" }} custom="KK"> <ChildComponent /> <ChildComponent> <GrandChildComponent /> </ChildComponent> </Component> // NOTE: Component 中間的組件會被作為 props.children 帶入 Component <Component key="xxx" ref={ref => {}} onEvent={event => {}} style={{ marginTop: "10px" }} custom="KK" children={<> <ChildComponent /> <ChildComponent> <GrandChildComponent /> </ChildComponent> </>}> </Component> $('#key').on('EventName', (event) => { }) const handleClick = () => { console.log('click') } <Button onClick={handleClick}>Foo</Button> <Button onClick={handleClick()}>Foo</Button> const res = handleClick() <Button onClick={res}>Foo</Button> ``` ### 組件類型 * Functional Component (aka PureComponent, Presentational Component) ```jsx= const FunctionalComponent = (props) => { const { name, children } = props return ( <div> <div>Hello, {name}!</div> {children} </div> ) } <FunctionalComponent name={name}> <div>FC</div> </FunctionalComponent> <div id="xxx.ooo.yyy"> <div id="xxx.ooo.yyy.qqq">Hello, KK!</div> <div id="xxx.ooo.yyy.www">FC</div> </div> ``` * Class Component ```jsx= class ClassComponent extends React.Component { state = { count: 0, name: null, item: { name: null, value: 0 } } handleHi = () => { // this.setState({ count: this.state.count + 1 }) this.setState({ item: { value: 1 } }) // { // count: 0, // name: null, // item: { // value: 1 // } // } } render() { // const handleHiU = () => { // this.setState({ count: this.state.count + 1 }) // } return ( <div> <div>Hello, {this.props.name}!</div> <button onClick={this.handleHi}>Hi!</button> <button onClick={() => { console.log('hi') }}>Hi!</button> <div>{this.state.count}</div> {this.props.children} </div> ) } } <ClassComponent name="KK"> <div>FC</div> </ClassComponent> ``` > ReactDOM.render() 將 Virtual DOM 渲染到實際的 DOM 上面 ```jsx= ReactDOM.render(<App />, document.getElementById('root')) ``` > React.createElement() 此為 js 寫法,為了方便開發,故使用 `jsx` 語法糖 ```javascript= class Hello extends React.Component { render() { return React.createElement('div', null, `Hello ${this.props.toWhat}`) } } ReactDOM.render( React.createElement(Hello, {toWhat: 'World'}, null), document.getElementById('root') ) ``` ```jsx= class Hello extends React.Component { render() { return <div>{`Hello ${this.props.toWhat}`}</div> } } ReactDOM.render( <Hello toWhat="World" />, document.getElementById('root') ) ``` > component 可以是多個組件的集合,但是集合內的組件均需要給予 key ```jsx= const ArrayComponent1 = (props) => { const todos = ['eat', 'sleep', 'watch animate'] return todos.map(todo => <div key={todo}>{todo}</div>) } const ArrayComponent2 = (props) => { const todos = ['eat', 'sleep', 'watch animate'] return todos.map((todo, idx) => <div key={idx}>{todo}</div>) } ``` > 如果狀態需要更新陣列型態的話 ```jsx= // AddItem this.setState({ items: [...this.state.items, newItem] }) // UpdateItem this.setState({ items: [ ...this.state.items.slice(0, idx), updatedItem, ...this.state.items.slice(idx + 1), ] }) // DeleteItem this.setState({ items: [ ...this.state.items.slice(0, idx), ...this.state.items.slice(idx + 1), ] }) ``` > Extend Custom Component ```jsx= class Button extends React.Component { } class MainButton extends Button { constructor({background, ...parentProps}) { super(parentProps) } } <Button onClick={}>Foo</Button> <MainButton background="red" onClick={}>Foo</MainButton> ``` > 命名習慣 ```jsx= // props 名稱常以 onCallback 這個模式命名 const MyButton = ({ onClick, children }) => { return <button onClick={onClick}>{children}</button> } // onCallback 內的處理函式常以 handleCallback 這個模式命名 const MyButton = ({ children }) => { return <button onClick={handleClick}>{children}</button> } ``` ## 06/13(四)18:30 - 21:30 ### 受控組件 ```jsx= class ControlledForm extends React.Component { state = { value1: '', value2: '', value3: '', } handleInput1Change = (e) => { this.setState({value1: e.currentTarget.value}) } handleInput2Change = (e) => { this.setState({value2: e.currentTarget.value}) } handleInput3Change = (e) => { this.setState({value3: e.currentTarget.value}) } render() { return ( <form> <input value={this.state.value1} onChange={this.handleInput1Change} /> <input value={this.state.value2} onChange={this.handleInput2Change} /> <input value={this.state.value3} onChange={this.handleInput3Change} /> </form> ) } ``` ### 不受控組件 ```jsx= class UncontrolledForm extends React.Component { input1 = null input2 = null input3 = null render() { return ( <form> <input ref={ref => this.input1 = ref} /> <input ref={ref => this.input2 = ref} /> <input ref={ref => this.input3 = ref} /> </form> ) } ``` ### Component Lifecycle ![](https://i.imgur.com/0AgQDgX.png) ```jsx= class ClassButton extends React.Component { static getDerivedStateFromProps = (props, state) => { // RARE // use memorize or componentDidUpdate instead // return null | new_state } shouldComponentUpdate = (nextProps, nextState) => { // decide component to re-render or not // return true | false } getSnapshotBeforeUpdate = (prevProps, prevState) => { // RARE // get the snapshot before updating the component // ex. todo list, updating component will // return null | snapshot } componentDidMount = () => { // do something after mounting the component // ex. fetch initial data } componentDidUpdate = (prevProps, prevState, snapshot) => { // do something after updating the component // ex. fetch another data } componentWillUnmount = () => { // do something before unmounting the component // ex. clear timer } render() { return <button>Click me!</button> } } ``` ### React Hook #### State hook ```jsx= // formula const [state, setState] = useState(initialState) ``` ```jsx= const Button = () => { const [count, setCount] = useState(0) return <button onClick={() => setCount(count + 1)}>{count}</button> } ``` ```jsx= // quiz const ExplosionButton = (props) => { // TODO: click button 10 times and explode! return <button>還有 10 次爆炸!</button> } ``` #### Effect hook ```jsx= // formula useEffect(() => { // do something // callback before unmounting return () => {} }, dependencies) ``` ```jsx= const Title = ({title}) => { useEffect(() => { const timer = setInterval(() => console.log(title), 1000) return () => clearInterval(timer) }, []) return <div>{title}</div> } const Title1 = ({title}) => { useEffect(() => { const timer = setInterval(() => console.log(title), 1000) return () => clearInterval(timer) }, [title]) return <div>{title}</div> } const Table = ({ currentPage }) => { useEffect(() => { // fetch page console.log(currentPage) }, [currentPage]) } ``` ```jsx= // quiz const Timer1 = () => { const [count, setCount] = useState(0) useEffect(() => { const timer = setInterval(() => { console.log(count + 1) setCount(count + 1) }, 1000) return () => clearInterval(timer) }, [count]) return <div>{count}</div> } const Timer2 = () => { const [count, setCount] = useState(0) useEffect(() => { const timer = setInterval(() => { console.log(count + 1) setCount(count + 1) }, 1000) return () => clearInterval(timer) }, []) return <div>{count}</div> } ``` ### Custom hook ```jsx= const Component = () => { const [query, setQuery] = useQueryParams(window.location) } ``` ```jsx= const useKK = (times) => { const [left, setLeft] = useState(times) useEffect(() => { console.log(`KK left ${times} times.`) }, [times]) return [left, () => setLeft(left - 1)] } const Button = () => { const [times, useTimes] = useKK(100) return <button onClick={() => useTimes()}>click me</button> } ``` ```jsx= // quiz const useTimer = (interval) => { const [state, setState] = useState('stop') const start = () => setState('start') const pause = () => setState('pause') const stop = () => setState('stop') return [state, start, pause, stop] } const Player = () => { const [timerState, onStart, onPause, onStop] = useTimer() return <div> <button onClick={onStart}>Play</button> <button>Pause</button> <button>Stop</button> {timerState} </div> } ``` ```jsx= // homework // fucking hard, think your best const useInterval = (callback, delay) => { const savedCallback = useRef(); // Remember the latest callback. useEffect(() => { savedCallback.current = callback; }, [callback]); // Set up the interval. useEffect(() => { const tick = () => { savedCallback.current(); } if (delay !== null) { let id = setInterval(tick, delay); return () => clearInterval(id); } }, [delay]); } ``` > reference [here](https://overreacted.io/making-setinterval-declarative-with-react-hooks/) ## 06/20(四)18:30 - 21:30 實際開發 Just Answer Now App ### 持續學習 * Higher-Order Component (HoC) * React Context * React Router * Redux