-工具类
node、npm、eslint、pretter
语法类
es5、es6
概念类
SPA:单页面应用
MPA:多页面应用
PWA:渐进式 Web App
PWA组成技术
-
Service Worker:服务工作线程
1.1 常驻内存运行
1.2 代理网络请求
1.3 依赖HTTPS
// npm install serve -g 全局安装服务模块 // 在项目目录下执行 serve 快速启动http本地服务器 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 执行后 5s 再激活 执行 activate /* event.waitUntil(new Promise(resolve => { setTimeout(resole, 5000) })) */ // 强制停止旧的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 优化回调地狱
2.2 async/await 语法同步化
2.3 Service Worker的API
// callback回调方式 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 转换 async function readXML(filename) { const content = await readFile(filename) const xml = await parseXML(content) return xml }
-
fetch:网络请求
3.1 比XMLHttpRequest更简洁
3.2 Promise风格
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 实现 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:支持资源的缓存系统
4.1 缓存资源(css/scripts/image)
4.2 依赖 Service Worker 代理网络请求
4.3 支持离线程序运行
// 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 依赖用户授权
5.2 适合在Service Worker中推送
// 在页面上下文中查看用户授权 Notification.permission // defalut 默认 granted 允许 denied 拒绝 // 在页面上下文中弹出授权 Notification.requestPermission().then(permission => console.log(permission)) // 弹出通知消息 new Notification("HELLO NOTIFICATION", {body: 'this is from console'}) // 在 Service Worker 上下文 中不允许弹出授权请求 // 在 Service Worker 上下文中 弹出通知消息 self.registration.showNotification("HELLO NOTIFICATION", {body: 'this is from sw'})
在webpack中开启PWA(create-react-app中已经集成service worker)
谷歌推出 workbox
效率类
原则类
eject
npm run eject
打开create-react-app
的webpack.config,js
React 最新特性
-
context
定义:提供一种方式,能够让数据在组件树中传递而不必一级一级手动传递
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})} > 点击-1 </button> <button type="button" onClick={() => this.setState({online: !online})} > 点击切换 </button> <Middle /> </OnlineContext.Provider> </BatteryContext.Provider> ) } }
-
ContextType简化
context
的使用import React, { createContext } from 'react' const BatteryContext = createContext(90) // 90 为默认值 // 第三级组件 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})} > 点击-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 } // 捕捉异常的静态方法 static getDerivedStateFromError() { return { hasError: true } } // 捕获异常的生命周期函数 /* 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:避免子组件的重复渲染
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
简化状态的写法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})}} > 点击 ({count}) </button> ) } } export default App
import React, { useState } from 'react' function App() { const [ count, setCount ] = useState(0) return ( <button type="button" onClick={() => {setCount( count + 1)}} > 点击 ({count}) </button> ) } export default App
npm 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)}} > 点击 ({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})}} > 点击 ({count}) Size: {size.width}x{size.height} </button> ) } } export default App
import 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)}} > 点击 ({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> ) } } // 在函数组件中使用 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)}} > 点击 ({count}) </button> <CountContext.Provider value={count}> <Foo/> <Bar/> <Counter/> </CountContext.Provider> </div> ) } export default App
-
使用
useMemo
优化性能,避免子组件重复渲染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 返回的是一个函数 简写成 useCallback /* const onClick = useMemo(() => { return () => { console.log('Click') } }, []) */ return ( <div> <button type="button" onClick={() => {setCount( count + 1)}} > 点击 ({count}), double: {double} </button> <Counter count={count} onClick={onClick}/> </div> ) } export default App
-
使用
useRef
import React, { PureComponent, useState, useMemo, memo, useCallback, useRef } from 'react' /* 函数组件不能别 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)}} > 点击 ({count}), double: {double} </button> <Counter ref={counterRef} count={count} onClick={onClick}/> </div> ) } export default App
import React, { PureComponent, useEffect, useState, useMemo, memo, useCallback, useRef } from 'react' /* 函数组件不能别 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)}} > 点击 ({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函数 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函数,返回 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)}} > 点击 ({count}) </button> { Counter } </div> ) } export default App
Hooks的使用规则
1. 只在最顶层中使用hooks,不在循环、判断等条件下使用
2. 只在函数组件中使用,不在普通函数中使用
Hooks常见问题
对传统React编程影响
-
生命周期
图片.png
-
constructor:在函数组件中用
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:在函数组件中 使用
memo
-
render:函数组件本身返回 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>
}
- 如何强制更新一个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
-
状态容器与数据流管理
-
redux的三大原则
2.1 单一数据源 2.2 状态不可变 2.3 纯函数修改状态
-
没有redux的世界,纯 Hooks 开发 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 })) }, []) // 读取的副作用必须在写入之前才不会永远是空数组 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
网友评论