1.自定义Hooks
注意:自定义Hooks本质上还是实现一个函数,关键在于实现逻辑。
1.1 setTitle hook
import { useEffect } from 'react'
const useTitle = (title) => {
useEffect(() => {
document.title = title
}, [])
return
}
export default useTitle
const App = () => {
useTitle('new title')
return <div>home</div>
}
1.2 update hook
import { useState } from 'react'
const useUpdate = () => {
const [, setFlag] = useState()
const update = () => {
setFlag(Date.now())
}
return update
}
export default useUpdate
// 实际使用
const App = (props) => {
// ...
const update = useUpdate()
return <div>
{Date.now()}
<div><button onClick={update}>update</button></div>
</div>
}
1.3 useScroll hook
import { useState, useEffect } from 'react'
const useScroll = (scrollRef) => {
const [pos, setPos] = useState([0,0])
useEffect(() => {
function handleScroll(e){
setPos([scrollRef.current.scrollLeft, scrollRef.current.scrollTop])
}
scrollRef.current.addEventListener('scroll', handleScroll)
return () => {
scrollRef.current.removeEventListener('scroll', handleScroll)
}
}, [])
return pos
}
export default useScroll
// 用法
import React, { useRef } from 'react'
import { useScroll } from 'hooks'
const Home = (props) => {
const scrollRef = useRef(null)
const [x, y] = useScroll(scrollRef)
return <div>
<div ref={scrollRef}>
<div className="innerBox"></div>
</div>
<div>{ x }, { y }</div>
</div>
}
2. Hooks VS HOC
- Hook最典型的就是取代掉生命周期中大多数的功能,可以把更相关的逻辑放在一起,而非零散在各个生命周期方法中;
- 高阶组件可以将外部的属性功能到一个基础 Component 中,更多作为扩展能力的插件(如 react-swipeable-views中的 autoPlay 高阶组件,通过注入状态化的 props 的方式对组件进行功能扩展,而不是直接将代码写在主库中);
- Hook的写法可以让代码更加紧凑,更适合做 Controller 或者需要内聚的相关逻辑,一般与目标组件内强依赖,HOC更加强调对原先组件能力的扩展;
3. 异步组件
随着项目的增长,代码包也会随之增长,尤其是在引入第三方库的情况下,要避免因体积过大导致加载时间过长。
React16.6中,引入了 React.lazy 和 React.Suspense 两个API,再配合动态import()语法就可以实现组件代码打包分割和异步加载。
传统模式:渲染组件 -> 请求数据 -> 再渲染组件
异步组件:请求数据 -> 渲染组件;
// demo
import React, { lazy, Suspense } from 'react';
// lazy 和 Suspense 配套使用,react原生支持代码分割
const About = lazy(() => import(/* webpackChunkName: "about" */'./About'));
class App extends React.Component {
render() {
return (
<div className="App">
<h1>App</h1>
<Suspense fallback={<div>loading</div>}>
<About />
</Suspense>
</div>
);
}
}
export default App;
3.1 前置基础
- 动态import
相对于静态import的 import XX from XX,动态import指在运行时加载。
import('./test.js').then(test => {
// ...
});
// 可见,是实现了Promsie规范的,回调函数为返回的模块
- 错误边界
React V16中引入,部分UI的JS错误不会导致整个应用崩溃;错误边界时一种 React 组件,错误边界在 渲染期间、生命周期方法和整个组件树的构造函数 中捕获错误,且会渲染出备用UI而不是崩溃的组件。
// comp ErrorBoundary
import React from 'react'
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
console.log(error, errorInfo)
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary
// comp App
import React, from 'react';
import ErrorBoundary from './ErrorBoundary'
class App extends React.Component {
state = {
count: 1
}
render() {
const { count } = this.state
if (count === 3) {
throw new Error('I crashed!');
}
return (
<ErrorBoundary>
<h1>App</h1>
<p>{count}</p>
<button onClick={() => this.setState({ count: count + 1 })}>add</button>
</ErrorBoundary>
)
}
}
export default App;
3.2 手写异步组件
Suspense 组件需要等待异步组件加载完成再渲染异步组件的内容。
- lazy wrap住异步组件,React第一次加载组件的时候,异步组件会发起请求,并且抛出异常,终止渲染;
- Suspense里面有 compenentDidCatch生命周期函数,异步组件抛出异常会触发这个函数,然后改变状态使其渲染fallback参数传入的组件;
- 异步组件的请求成功返回之后,Suspense组件再次改变状态使其渲染正常子组件(即异步组件);
// comp About
const About = lazy(() => new Promise(resolve => {
setTimeout(() => {
resolve({
default: <div>component content</div>
})
}, 1000)
}))
// comp Suspense
import React from 'react'
class Suspense extends React.PureComponent {
/**
* isRender 异步组件是否就绪,可以渲染
*/
state = {
isRender: true
}
componentDidCatch(e) {
this.setState({ isRender: false })
e.promise.then(() => {
/* 数据请求后,渲染真实组件 */
this.setState({ isRender: true })
})
}
render() {
const { fallback, children } = this.props
const { isRender } = this.state
return isRender ? children : fallback
}
}
export default Suspense
// comp lazy
import React, { useEffect } from 'react'
export function lazy(fn) {
const fetcher = {
status: 'pending',
result: null,
promise: null,
}
return function MyComponent() {
const getDataPromise = fn()
fetcher.promise = getDataPromise
getDataPromise.then(res => {
fetcher.status = 'resolved'
fetcher.result = res.default
})
useEffect(() => {
if (fetcher.status === 'pending') {
throw fetcher
}
}, [])
if (fetcher.status === 'resolved') {
return fetcher.result
}
return null
}
}
// 实现的效果与React支持内容保持一致
import React, {Suspese, lazy} from 'react'
const About= lazy(() => { import('../About') });
class App extends React.Component {
render() {
/**
* 1. 使用 React.Lazy 和 import() 来引入组件
* 2. 使用<React.Suspense></React.Suspense>来做异步组件的父组件,并使用 fallback 来实现组件未加载完成时展示信息
* 3. fallback 可以传入html,也可以自行封装一个统一的提示组件
*/
return (
<div>
<Suspense
fallback={
<Loading />
}
>
<About />
</Suspense>
</div>
)
}
}
export default ReactComp;