-工具類
node、npm、eslint、pretter
語法類
es5、es6
概念類
SPA:單頁面應(yīng)用
MPA:多頁面應(yīng)用
PWA:漸進(jìn)式 Web App
PWA組成技術(shù)
-
Service Worker:服務(wù)工作線程
1.1 常駐內(nèi)存運(yùn)行
1.2 代理網(wǎng)絡(luò)請求
1.3 依賴HTTPS
// npm install serve -g 全局安裝服務(wù)模塊 // 在項目目錄下執(zhí)行 serve 快速啟動http本地服務(wù)器 localhost:5000 // index.html <html> <head> <title>learn PWA</title> </head> <body> <script> // 注冊 service worker navigator.serviceWorker.register('./sw.js', { scope: '/' }).then(registration => { console.log(registration) }, error => { console.error(error) }) </script> </body> </html>// sw.js self.addEventListener('install', event => { console.log('install', event) // 在 install 執(zhí)行后 5s 再激活 執(zhí)行 activate /* event.waitUntil(new Promise(resolve => { setTimeout(resole, 5000) })) */ // 強(qiáng)制停止舊的service worker 激活新的 service worker event.waitUntil(self.skipWaiting()) }) self.addEventListener('activate', event => { console.log('activate', event) // 同樣包含 event.waitUntil() 與 install 中用法一樣 event.waitUntil(self.clients.claim()) }) self.addEventListener('fetch', event => { console.log('fetch', event) })
-
Promise:”承諾”控制流
2.1 優(yōu)化回調(diào)地獄
2.2 async/await 語法同步化
2.3 Service Worker的API
// callback回調(diào)方式 readFile(filename, (err, content) => { parseXML(content, (err, xml) => { // }) }) readFile(filename).then(content => parseXML(content)).then(xml => {}, err => {}) // 推薦使用 readFile(filename).then(content => parseXML(content)).then(xml => {}).catch(err => {}) Promise.resolve(1) // 等價于 new Promise(resolve => resolve(1)) Promise.reject(error) // 等價于 new Promise((resolve, reject) => reject(error)) Promise.all() Promise.race() // 替代 Promise 需要 babel 轉(zhuǎn)換 async function readXML(filename) { const content = await readFile(filename) const xml = await parseXML(content) return xml } -
fetch:網(wǎng)絡(luò)請求
3.1 比XMLHttpRequest更簡潔
3.2 Promise風(fēng)格
3.3 依舊存在不足
<html> <head> <title>learn PWA</title> </head> <body> <script> // 原始ajax請求 const xhr = new XMLHttpRequset() xhr.responseType = 'json' xhr.onreadstatechange = () => { if(xhr.readyState === 4){ if(xhr.status >= 200 && xhr.status < 300) { console.log(xhr.response) } } } xhr.open('GET', './useinfo.json', true) xhr.send(null) // fetch 實現(xiàn) const req = new Request('./useinfo.json', { method: 'GET', headers: new Headers(), credentials: 'include' }) /* fetch('./useinfo.json', { method: 'GET', headers: new Headers(), credentials: 'include' }) */ fetch(req) .then(res => res.json()) .then(info => console.log(info)) // 更好的方案 axios </script> </body> </html> -
cache API:支持資源的緩存系統(tǒng)
4.1 緩存資源(css/scripts/image)
4.2 依賴 Service Worker 代理網(wǎng)絡(luò)請求
4.3 支持離線程序運(yùn)行
// index.html <html> <head> <title>learn PWA</title> </head> <body> <h1> hello PWA </h1> <script> // 注冊 service worker navigator.serviceWorker.register('./sw.js', { scope: '/' }).then(registration => { console.log(registration) }, error => { console.error(error) }) </script> </body> </html>// sw.js const CACHE_NAME = 'cache-v1' self.addEventListener('install', event => { console.log('install', event) event.waitUntil(caches.open(CACHE_NAME).then(cache => { cache.addAll([ './', './index.css' ]) })) }) self.addEventListener('activate', event => { console.log('activate', event) // 同樣包含 event.waitUntil() 與 install 中用法一樣 event.waitUntil(caches.keys().then(cacheNames => { return Promise.all(cacheNames.map(cacheName => { if(cacheName !== CACHE_NAME){ return caches.delete(cacheName) } })) })) }) self.addEventListener('fetch', event => { console.log('fetch', event) event.respondwWidth(caches.open(CACHE_NAME).then(cache => { return cache.match(event.request).then(res => { if(res) { return res } return fetch(event.request).then(res => { cache.put(event.request, res.clone()) return res }) }) })) }) -
Notification API:消息推送
5.1 依賴用戶授權(quán)
5.2 適合在Service Worker中推送
// 在頁面上下文中查看用戶授權(quán) Notification.permission // defalut 默認(rèn) granted 允許 denied 拒絕 // 在頁面上下文中彈出授權(quán) Notification.requestPermission().then(permission => console.log(permission)) // 彈出通知消息 new Notification("HELLO NOTIFICATION", {body: 'this is from console'}) // 在 Service Worker 上下文 中不允許彈出授權(quán)請求 // 在 Service Worker 上下文中 彈出通知消息 self.registration.showNotification("HELLO NOTIFICATION", {body: 'this is from sw'})在webpack中開啟PWA(create-react-app中已經(jīng)集成service worker)
谷歌推出 workbox
效率類
原則類
eject
npm run eject 打開create-react-app的webpack.config,js
React 最新特性
-
context
定義:提供一種方式,能夠讓數(shù)據(jù)在組件樹中傳遞而不必一級一級手動傳遞
import React, { createContext } from 'react' const BatteryContext = createContext() const OnlineContext = createContext() // 第三級組件 class Leaf extend Component { render() { return ( <BatteryContext.Consumer> { battery => ( <OnlineContext.Consumer> { online => <h1>Battery: {battery}, Online: {String(online)}</h1> } </OnlineContext.Consumer> ) } </BatteryContext.Consumer> ) } } // 中間組件 class Middle extend Component { render() { return <Leaf/> } } // 頂級組件 class App extend Component { state = { battery: 60, online: false } render() { const { battery, online } = this.state return ( <BatteryContext.Provider value={battery }> <OnlineContext.Provider value={online}> <button type="button" onclick={() => this.setState({battery: battery - 1})} > 點(diǎn)擊-1 </button> <button type="button" onClick={() => this.setState({online: !online})} > 點(diǎn)擊切換 </button> <Middle /> </OnlineContext.Provider> </BatteryContext.Provider> ) } }
-
ContextType簡化
context的使用import React, { createContext } from 'react' const BatteryContext = createContext(90) // 90 為默認(rèn)值 // 第三級組件 class Leaf extends Component { static ContextType = BatteryContext render() { const battery = this.context return ( <h1>Battery: {battery}</h1> ) } } // 中間組件 class Middle extends Component { render() { return <Leaf/> } } // 頂級組件 class App extends Component { state = { battery: 60 } render() { const { battery, online } = this.state return ( <BatteryContext.Provider value={battery }> <button type="button" onClick={() => this.setState({battery: this.battery - 1})} > 點(diǎn)擊-1 </button> <Middle /> </BatteryContext.Provider> ) } }
-
lazy 與 Suspense:組件的懶加載
// about 組件 import React, { Component } from 'react' export default class About extends Component { render () { return ( <h1>About</h1> ) } }// APP 組件 import React, { Component, lazy, Suspense } from 'react' const About = lazy(() => import(/* webpackChunkName: "about" */'./About.jsx')) class App extends Component { state = { hasError: false } // 捕捉異常的靜態(tài)方法 static getDerivedStateFromError() { return { hasError: true } } // 捕獲異常的生命周期函數(shù) /* componentDidCatch () { this,setState({ hasErrir: true }) } */ render () { return ( if (this.state.hasError) { return <div>error</div> } <div> <Suspense fallback={<div>loading</div>}> <About></About> </Suspense> </div> ) } } export default App
-
memo 和 PureComponent:避免子組件的重復(fù)渲染
import React, { Component, PureComponent} from 'react' /* class Foo extends Component { shouldComponentUpdate(nextProps, nextState) { if (nextProps.name === this.props.name) { return false } return true } */ class Foo extends PureComponent { /* shouldComponentUpdate(nextProps, nextState) { if (nextProps.name === this.props.name) { return false } return true } */ render() { console.log('Foo render') return null } } class App extends Component { state = { count: 0 } render() { return ( <div> <button type="button" onClick={() => {this.setState({ count: this.count + 1 })}} >Add</button> <Foo name="xinmin" /> </div> ) } }import React, { Component, PureComponent, memo } from 'react' const Foo = memo((props) => { console.log('Foo render') return <div>{props.person.age}</div> }) class App extends Component { state = { count: 0, person: { age: 1 } } render() { return ( <div> <button type="button" onClick={() => { person.age++ this.setState({ person }) }} >Add</button> <Foo person={person} /> </div> ) } }
Hooks
-
使用
useState簡化狀態(tài)的寫法import React, { component } from 'react' class App extends Component { state = { count: 0 } render() { const { count } = this.state return ( <button type="button" onClick={() => {this.setState({count: count + 1})}} > 點(diǎn)擊 ({count}) </button> ) } } export default Appimport React, { useState } from 'react' function App() { const [ count, setCount ] = useState(0) return ( <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}) </button> ) } export default Appnpm i eslint-plugin-react-hooks -D可以檢測Hooks的錯誤,需要在package.json中配置"eslintConfig": { "extends": "react-app", "plugins": [ "react-hooks/rules-of-hooks": "error" ] }import React, { useState } from 'react' function App(props) { const [ count, setCount ] = useState(() => { console.log('initial count') return props.defaultCount || 0 }) return ( <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}) </button> ) } export default App
-
使用
useEffect簡化副作用的寫法import React, { component } from 'react' class App extends Component { state = { count: 0, size: { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } } onResize = () => { this.setState({ size: { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } }) } componentDidMount() { document.title = this.state.count window.addEventListenner('resize', this.onResize, false) } componentWillMount() { window.removeEventListener('resize', this.onResize, false) } componentDidUpdate() { document.title = this.state.count } render() { const { count, sizehis.state return ( <button type="button" onClick={() => {this.setState({count: count + 1})}} > 點(diǎn)擊 ({count}) Size: {size.width}x{size.height} </button> ) } } export default Appimport React, { useState, useEffect } from 'react' function App(props) { const [ count, setCount ] = useState(0) const [ size, setSize ] = useState({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }) const onResize = () => { setSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }) } useEffect(() => { document.title = count }) useEffect(() => { window.addEventListener('resize', onResize, false) return () => { window.removeEventListener('resize', onResize, false) } }, []) return ( <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}) Size: {size.width}x{size.height} </button> ) } export default App -
使用
useContext跨層級組件傳值import React, { useState, createContext, useContext } from 'react' // 在類組件中使用context class Foo extends React.Component { render() { return ( <CountContext.Consumer> { count => <h1>count: {count}</h1> } </CountContext.Consumer> ) } } // 在類組件中使用 contextType 簡化 context 的使用 class Bar extends React.Component { static contextType = CountContext render() { const count = this.context return ( <h1>{count}</h1> ) } } // 在函數(shù)組件中使用 context function Counter() { const count = useContext(CountCountext) return ( <h1>{count}</h1> ) } const CountContext = createContext() function App(props) { const [ count, setCount ] = useState(0) return ( <div> <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}) </button> <CountContext.Provider value={count}> <Foo/> <Bar/> <Counter/> </CountContext.Provider> </div> ) } export default App -
使用
useMemo優(yōu)化性能,避免子組件重復(fù)渲染import React, { useState, useMemo, memo, useCallback } from 'react' const Counter = memo(function Counter(props) { console.log('Counter render!') return ( <h1 onClick={props.onClick}>count:{props.count}</h1> ) }) function App(props) { const [ count, setCount ] = useState(0) const double = useMemo(() => { return count*2 }, [count===3]) const onClick = useCallback(() => { console.log('Click') }, []) // 如果 useMemo 返回的是一個函數(shù) 簡寫成 useCallback /* const onClick = useMemo(() => { return () => { console.log('Click') } }, []) */ return ( <div> <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}), double: {double} </button> <Counter count={count} onClick={onClick}/> </div> ) } export default App
-
使用
useRefimport React, { PureComponent, useState, useMemo, memo, useCallback, useRef } from 'react' /* 函數(shù)組件不能別 ref 獲取 const Counter = memo(function Counter(props) { console.log('Counter render!') return ( <h1 onClick={props.onClick}>count:{props.count}</h1> ) }) */ class Countter extends PureComponent { speak() { console.log(`now counter is:${this.props.count}`) } render() { const { props } = this return ( <h1 onClick={props.onClick}>count:{props.count}</h1> ) } } function App(props) { const [ count, setCount ] = useState(0) const counterRef = useRef() const double = useMemo(() => { return count*2 }, [count===3]) const onClick = useCallback(() => { console.log('Click') //console.log(counterRef.current) counterRef.current.speak() }, [counterRef]) return ( <div> <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}), double: {double} </button> <Counter ref={counterRef} count={count} onClick={onClick}/> </div> ) } export default Appimport React, { PureComponent, useEffect, useState, useMemo, memo, useCallback, useRef } from 'react' /* 函數(shù)組件不能別 ref 獲取 const Counter = memo(function Counter(props) { console.log('Counter render!') return ( <h1 onClick={props.onClick}>count:{props.count}</h1> ) }) */ class Countter extends PureComponent { speak() { console.log(`now counter is:${this.props.count}`) } render() { const { props } = this return ( <h1 onClick={props.onClick}>count:{props.count}</h1> ) } } function App(props) { const [ count, setCount ] = useState(0) const counterRef = useRef() const it = useRef() const double = useMemo(() => { return count*2 }, [count===3]) const onClick = useCallback(() => { console.log('Click') //console.log(counterRef.current) counterRef.current.speak() }, [counterRef]) useEffect(() => { it.current = setInterval(() => { setCount(count => count + 1) }, 1000) }, []) useEffect(() => { if(count >= 10){ clearInterval(it.current) } },[]) return ( <div> <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}), double: {double} </button> <Counter ref={counterRef} count={count} onClick={onClick}/> </div> ) } export default App -
自定義的Hooks
import React, { PureComponent, useEffect, useState, useMemo, memo, useCallback, useRef } from 'react' // 自定義hook函數(shù) function useCount(defaultCount) { const [count, setCount] = useState(defaultCount) const it = useRef() useEffect(() => { it.current = setInterval(() => { setCount(count => count + 1) }, 1000) }, []) useEffect(() => { if(count >= 10){ clearInterval(it.current) } }) return [count, setCount] } // 自定義hook函數(shù),返回 jsx function useCounter(count) { return ( <h1>{count}</h1> ) } function App(props) { const [ count, setCount ] = useCount(0) const Counter = useCounter(count) return ( <div> <button type="button" onClick={() => {setCount( count + 1)}} > 點(diǎn)擊 ({count}) </button> { Counter } </div> ) } export default App
Hooks的使用規(guī)則
1. 只在最頂層中使用hooks,不在循環(huán)、判斷等條件下使用
2. 只在函數(shù)組件中使用,不在普通函數(shù)中使用
Hooks常見問題
對傳統(tǒng)React編程影響
-
生命周期
圖片.pngconstructor:在函數(shù)組件中用
useState來初始化-
getDerivedStateFromProps
class Counter extends Component { state = { overflow: false } static getDerivedStateFromProps(props, state) { if(props.count > 10) { return { overflow: true } } } } function Counter(props) { const [overFlow, setOverflow] = useState(false) if(props.count > 10) { setOverflow(true) } } shouldComponentUpdate:在函數(shù)組件中 使用
memorender:函數(shù)組件本身返回 jsx
componentDidMount
componentWillUnmount
-
componentDidUpdate
function App() { useEffect(() => { // componentDidMount return () => { // componentWillUnmount } }, []) let renderCounter = useRef(0) renderCounter.current++ useEffect(() => { if(renderCounter > 1){ // componentDidUpdate } }) }
- 類成員變量如何映射到Hooks?
class App {
it = 0
}
funtion App() {
const it = useRef(0)
}
- Hooks中如何獲取歷史props和state?
function Counter() {
const [count, setCount] = useState(0)
const prevCountRef = useRef()
useEffect(() => {
prevCountRef.current = count
})
const prevCount = prevCountRef.current
return <h1>Now: {count}, before: {prevCount}</h1>
}
- 如何強(qiáng)制更新一個hooks組件?
function Counter() {
const [count, setCount] = useState(0)
const [updater, setUpdater] = useState(0)
const prevCountRef = useRef()
function forceUpdater() {
setUpdater(updater => updater + 1)
}
useEffect(() => {
prevCountRef.current = count
})
const prevCount = prevCountRef.current
return <h1>Now: {count}, before: {prevCount}</h1>
}
Redux
狀態(tài)容器與數(shù)據(jù)流管理
-
redux的三大原則
2.1 單一數(shù)據(jù)源 2.2 狀態(tài)不可變 2.3 純函數(shù)修改狀態(tài) -
沒有redux的世界,純 Hooks 開發(fā) TodoList
.todo-list { width: 550px; margin: 300px auto; background: #fff; box-shadow: 0 2px 4px 0 rgba(0,0,0,0.2), 0 25px 50px 0 rgba(0,0,0,0.1) } .control h1 { width: 100%; font-size: 100px; text-align: center; margin: 0; color: rgba(175,47,47,0.15); } .control .new-todo { padding: 16px 16px 16px 60px; border: 0; outline: none; font-size: 24px; box-sizing: border-box; line-height: 1.4rem; box-shadow: inset 0 -2px 1px rgba(0,0,0,0.3); } .todos { margin: 0; padding: 0; list-style: none; } .todo-item { margin: 0; padding: 0; list-style: none; font-size: 24px; display: flex; align-item: center; } .todo-item input { display: block; width: 20px; height: 20px; margin: 0 20px; } .todo-item label { flex: 1; padding: 0; line-height: 1.2rem; display: block; } .line-item label.complete { text-decoration: line-through; } .todo-item button { border: 0; outline: 0; display: block; width: 40px; text-align: center; font-size: 30px; color: #cc9a9a; }import React, { useState, useCallback, useRef, memo } from 'react' import './App.css' let idSeq = Date.now() const LS_KEY = '_$-todos_' const Control = memo(function Control(props) { const { addTodo } = props const inputRef = useRef() const onSubmit = (e) => { e.preventDefault() const newText = inputRef.current.value.trim() if(newText.length === 0) { return } addTodo({ id: ++idSeq, value: newText, complete: false }) inputRef.current.value = '' } return ( <div className="control"> <h1> todos </h1> <form onSubmit={}> <input type="text" ref={inputRef} className="new-todo" placeholder="輸入新的待辦" /> </form> </div> ) }) const TodoItem = memo(function TodoItem(props) { const { todo: { id, text, complete }, toggleTodo, removeTodo } = props const onChange = () => { toggleTodo(id) } const onRemove = () => { removeTodo(id) } return ( <li className="todo-item"> <input type="checkbox" onChange={onChange} checked={complete}/> <label className={complete ? 'complete' : ''}>{text}</label> <button onClick={onRemove}>$#xd7</button> </li> ) }) const Todos = memo(funtion Todos(props) { const { todos, toggleTodo, removeTodo } = props return ( <ul> { todos.map(todo => { return (<todoItem key={todo.id} todo={todo} toggleTodo={toggleTodo} removeTodo={removeTodo} />) }) } </ul> ) }) function TodoList() { const [todos, setTodos] = useState([]) const addTodo = useCallback((todo) => { setTodos(todos => [...todos, todo]) },[]) const removeTodo = useCallback((id) => { setTodos( todos => todos.filter(todo => { return todo.id !== id })) }, []) const toggleTodo = useCallback((id) => { setTodos(todos => todos.map(todo => { return todo.id === id ? { ...todo, complete: !todo.complete } : todo })) }, []) // 讀取的副作用必須在寫入之前才不會永遠(yuǎn)是空數(shù)組 useEffect(() => { const todos = JSON.parse(localStorage.getItem(LS_KEY) || '[]') setTodos(todos) },[]) // 寫入 useEffect(() => { localStorage.setItem(LS_KEY, JSON.stringify(todos)) }, [todos]) return( <div className="todo-list"> <Control addTodo={addTodo}/> <Todos removeTodo={removeTodo} toggleTodo={toggleTodo} todos={todos}/> </div> ) } export default TodoList
