# 3/29 大一部課 ## Overview of React & TODOs - [x] React useContext - [x] Axios - [ ] Websocket - [ ] graphql - [ ] sql - [ ] docker - [ ] 其他...?(以下開放但部長不一定會XD) ## Higher order component (HOC) https://legacy.reactjs.org/docs/higher-order-components.html 假設今天要設計一個部落格,裡面有評論相關的component 首先,我們需要設計一個class來訂閱評論的database來render出評論: ```javascript= class CommentList extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { // "DataSource" is some global data source comments: DataSource.getComments() }; } componentDidMount() { // Subscribe to changes DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { // Clean up listener DataSource.removeChangeListener(this.handleChange); } handleChange() { // Update component state whenever the data source changes this.setState({ comments: DataSource.getComments() }); } render() { return ( <div> {this.state.comments.map((comment) => ( <Comment comment={comment} key={comment.id} /> ))} </div> ); } } ``` 然後設計一個用來訂閱blog的class: ```javascript= class BlogPost extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { blogPost: DataSource.getBlogPost(props.id) }; } componentDidMount() { DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ blogPost: DataSource.getBlogPost(this.props.id) }); } render() { return <TextBlock text={this.state.blogPost} />; } } ``` 這兩個component不完全一樣(使用datasource方法不一樣) 不過,其他setup的地方基本上都一樣: 連接時,新增一個listener用來監聽datasource 用setstate來改變data source的任何改變 中斷連線時,則移除listener 這時我們可能需要一個外部的function,可以處理各個component相同的部分(不然就會寫一大堆setstate),同時可以分享給各個會使用到datasource 的 component ==> High Order Component !!! 利用HOC的概念,我們可以設計一個function,用上述的component (或任何) 當作這個function的child component,並且會接收訂閱的data當作prop. ```javascript= const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments() ); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id) ); ``` 第一個參數是wrapped component. 第二個參數我們對於有興趣的data,用一個function把它提取出來(datasource, props). 當`CommentListWithSubscription`和`BlogPostWithSubscription`被render, `CommentList`和`BlogPost`會以prop的形式收到最新從datasource提取的data: ```javascript= // This function takes a component... function withSubscription(WrappedComponent, selectData) { // ...and returns another component... return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ... that takes care of the subscription... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... and renders the wrapped component with the fresh data! // Notice that we pass through any additional props return <WrappedComponent data={this.state.data} {...this.props} />; } }; } ``` > Note that a HOC doesn’t modify the input component, nor does it use inheritance to copy its behavior. Rather, a HOC composes the original component by wrapping it in a container component. A HOC is a pure function with zero side-effects. ## Other types of hooks ```javascript= const [square, setSquare] = useState(null); ``` ### User defined hook 和HOC的概念相似,假如需要access相同的資源,可以找出做相同事情的部分,寫一個新的function(名字開頭取成use+...),把setstate通通寫進function裡。 ### useEffect https://react.dev/reference/react/useEffect useEffect is a React Hook that lets you synchronize a component with an external system. => 不一定,也可能是更新與state有關的identities。 ```javascript= const [count, setCount] = useState(0) useEffect(() => { // 使用瀏覽器 API 更新文件標題 document.title = `You clicked ${count} times`; }); ``` 在class中會定義Onmount, WillUnmount等function用來處理這個component在render時該做什麼事情等等,在function裡就是使用useEffect 來達成類似的效果。 可以設定dependencies,當你指定的state狀態改變,就會去執行useEffect裡面的東西。 另外,也可以實作清除effect的function,就是return一個cleanup的function,譬如斷線等等。 ```javascript= import { useEffect } from 'react'; import { createConnection } from './chat.js'; function ChatRoom({ roomId }) { const [serverUrl, setServerUrl] = useState('https://localhost:1234'); const [count, setCount] = useState(0); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => { connection.disconnect(); }; }, [serverUrl, roomId]); // ... } ``` ### useRef https://react.dev/reference/react/useRef useRef is a React Hook that lets you reference a value that’s not needed for rendering. 可以用在一般變數,也可以用在DOM useRef並不會觸發rerender,因此適合用在不需要render的資料上 ```javascript= import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('You clicked ' + ref.current + ' times!'); } return ( <button onClick={handleClick}> Click me! </button> ); } ``` ```javascript= import { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); } ``` ### useContext https://react.dev/reference/react/useContext useContext is a React Hook that lets you read and subscribe to context from your component. 我們知道state必須放在最高層的component,而下層需要用到這個state的component則是透過prop傳下去。當有很多component需要用到同一個state,就會像下圖這樣。 ![image](https://hackmd.io/_uploads/B1t_AeEJC.png) 如果能夠把state提取出來,讓每個component經過一層就可以拿到state(平行化的概念),是不是更好呢? ![image](https://hackmd.io/_uploads/HJDcJWN10.png) 實作步驟: 1. Create a context. 2. Use that context from the component that needs the data. 3. Provide that context from the component that specifies the data. ```javascript= import { createContext, useContext } from 'react'; const ThemeContext = createContext(null); export default function MyApp() { return ( <ThemeContext.Provider value="dark"> <Form /> </ThemeContext.Provider> ) } function Form() { return ( <Panel title="Welcome"> <Button>Sign up</Button> <Button>Log in</Button> </Panel> ); } function Panel({ title, children }) { const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( <section className={className}> <h1>{title}</h1> {children} </section> ) } function Button({ children }) { const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( <button className={className}> {children} </button> ); } ``` 我們以官網的範例為例,level這個variable被使用很多次,每次使用Heading就要傳一次level。我們用 context 包好,在section使用這個context,這樣就可以不用呼叫level太多次。 context的格式不僅限於單一變數,也可以用object的形式傳遞:(連state也可以喔!) ```javascript= const ChatContext = createContext({ status: {}, me: "", signedIn: false, messages: [], sendMessage: () => {}, clearMessages: () => {}, }); const ChatProvider = (props) => { const [status, setStatus] = useState({}); const [me, setMe] = useState(savedMe || ""); const [signedIn, setSignedIn] = useState(false); const [messages, setMessages] = useState([]); ... return ( <ChatContext.Provider value={{ status, me, signedIn, messages, setMe, setSignedIn, sendMessage, clearMessages, displayStatus}} {...props} /> ); }; const useChat = () => useContext(ChatContext); export { ChatProvider, useChat }; ``` 在頂層(通常是index.js)使用chatProvider: ```javascript= import { ChatProvider } from '/path/to/useContext.js' const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <ChatProvider> <App /> </ChatProvider> </React.StrictMode> ); ``` 這時如果使用context的話,用`useChat()`就可以了! ```javascript= import { useChat } from '/path/to/useContext.js' const { status, sendMessage } = useChat() ``` ## Axios > Axios is a promise based HTTP client for the browser and Node.js. Axios makes it easy to send asynchronous HTTP requests to REST endpoints and perform CRUD operations. It can be used in plain JavaScript or with a library such as Vue or React. fetch 也可以實作 Restful API。 基本語法: ```javascript= // GET axios({ method: 'get', url: URL, //又要多寫一次headers headers: headers }) .then( (response) => console.log(response)) .catch( (error) => console.log(error)) // POST axios({ method: 'post', url: URL, data: data, headers: headers }) .then( (response) => console.log(response)) .catch( (error) => console.log(error)) ``` ```javascript= axios.request(config) axios.get(url[, config]) axios.delete(url[, config]) axios.head(url[, config]) axios.options(url[, config]) axios.post(url[, data[, config]]) axios.put(url[, data[, config]]) axios.patch(url[, data[, config]]) ``` ```javascript= // 使用 async, await async function getUser() { try { const response = await axios.get(URL); console.log(response); } catch (error) { console.error(error); } } ``` 好用的api網站:https://jsonplaceholder.typicode.com 使用`axios.create`讓程式碼簡短易讀: ```javascript= const instance = axios.create({ baseURL: 'https://jsonplaceholder.typicode.com/', headers: headers, // some header settings }) const startGame = async () => { const { data: { msg } } = await instance.post('/start', data) return msg } const guess = async (number) => { const { data: { msg } } = await instance.get( '/guess', { params: { number } }) return msg } const getData = async () => { const {data: {msg}} = await instance.get('/posts') } ``` 讀取回傳資料時,須注意回傳的payload格式(可以透過console.log,在browser打開console監聽) ## WebSocket 我們使用 Restful API時,與server建立連線,這種連線是一次性的。 當client滿足需求時(server發送了200 OK的response),這段通訊隨即宣告結束。一般的