React 一些高级特性和Hooks
zzzmj opened this issue · 0 comments
React新特性
目录
- Context和ContextType
- lazy和Suspense
- pureComponent 和 memo
- React Hooks
1. Context
React通过props自上到下传递数据,如果层级较深就会很麻烦
于是有了Context,它就像React中的全局变量,无需传递Props,组件树中就能进行数据传递
像react-redux中,传递store,就是利用了这个属性
1.1 使用Context
使用Context用到了生产者消费者模式
需要两个组件
<Provider>
生产者 (通常是父节点)<Consumer>
消费者 (通常是子节点)
例子
import React from 'react';
import Main from './Context/Main';
// 通过该createContext静态方法创建一个Context对象
// 这个对象包含了Provider和Context
// 传值是默认值,也可以不传
const ThemeContext = React.createContext({
background: 'red',
color: 'black'
})
class App extends React.Component() {
return (
<ThemeContext.Provider value="dark">
<Main/>
</ThemeContext.Provider>
);
}
class Main extends React.Component {
render() {
return (
<ToolBar />
)
}
}
class ToolBar extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{
(theme) => {
console.log('theme', theme)
}
}
</ThemeContext.Consumer>
)
}
}
export default App;
可以看见theme对象属性 跨了几个层级,传递到了ToolBar
看以看见 Provider 接收一个 value 属性,传递给Consumer组件使用
Context组件就像作用域链一样,Consumer使用 value 值的使用,会一层一层往上找,找到最近的Provider提供的value值
1.2 使用contextType
contextType其实就是语法糖,它约束了组件只能使用单一的context
在上面消费者写的形式不太优雅,通过contextType改写
class ToolBar extends React.Component {
// 套路,后面的ThemeContext是你定义的context
static contextType = ThemeContext
render() {
// 通过this.context可以拿到这个Provider提供的value值
const theme = this.context
return (
<h1>
{theme.background}
</h1>
)
}
}
相关文章:
聊一聊我对 React Context 的理解以及应用
2. lazy和Suspense
lazy可以动态引入组件,让组件在被需要的时候动态引入
const Counter = React.lazy(() => import('./Counter'))
需要配合Suspense使用,因为组件在引入的时候需要加载,在模块还没加载出来的时候
可以通过Suspense来渲染加载状态, fallback中包裹这个加载状态
import React, {Suspense} from 'react';
<Suspense fallback={<div>Loading...</div>}>
<Counter />
</Suspense>
异常捕获边界(Error boundaries)
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI
如果由于网络原因,模块加载失败,组件树崩溃,那么错误组件就会渲染出备用UI
class ErrorBoundary extends React.Component {
state = {
hasError: false
};
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 你可以自定义降级后的 UI 并渲染
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
3. pureComponent和memo
pureComponent就是帮忙实现了shouldComponentUpdate中浅层对象的对比
如果传入的props在浅层对比上没有变化,那么组件就不会被重新渲染,提高了性能。
memo是高阶组件,它和pureComponent的效果一样,它是针对于函数组件的
使用方法,用memo将函数组件包裹即可
const MyComponent = React.memo(function MyComponent(props) {
});
4. React Hooks
1. 为什么要用Hooks?
-
使用class的方式使得组件很难复用,一般只有两种方式
- render props
- 高阶组件
这两者方式,特别是第二种,比较麻烦,而且不好理解
-
复杂组件变得难以理解
组件越来越复杂的时候,会被逻辑和副作用充斥。
2. 简单的例子 和 userState
import React, { useState } from 'react';
function Example() {
// 声明一个叫 “count” 的 state 变量。
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
关键是userState这个函数
首先这个函数只接收一个参数, 就是初始state, useState(initState)
然后这个函数返回一对值,
- 第一个值是:当前状态
- 第二个值是:更新状态的函数 (类似于this.setState, 但是它不会合并新旧状态)
然后就能通过这个函数,完美实现了原来的this.state, this.setState
模式
什么时候我会用 Hook?
如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其它转化为 class。
现在你可以在现有的函数组件中使用 Hook。
3. 副作用 和 useEffect
在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。
我们统一把这些操作称为副作用
有两种常见的副作用
- 不需要清除的:比如网络请求,记录日志,变更DOM等
- 需要清除的:订阅,定时器等
3.1 不需要清除的副作用
在以往的class方式中,我们会在
componentDidMount、componentDidUpdate 和 componentWillUnmount中处理这些东西
现在使用 useEffect
来代替它们。
function Counter() {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
console.log(count)
})
return (
<div>
Count: {count}
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</div>
);
}
useEffect
做了什么? 会在第一次渲染之后和每次更新后调用
为什么要在组件内部调用useEffect
?将它放入组件内部可以直接使用state变量,这是利于到了闭包的特性
疑问1. 为什么componentDidMount 或 componentDidUpdate 会阻塞浏览器更新屏幕
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
3.2 需要清除的副作用
在class的方式中
// 在组件挂载后订阅
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
// 在组件销毁后取消订购
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
在hooks中,你可能认为要单独再使用一个函数来执行组件销毁后的操作
但设计仍然是在useEffect
之中,如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它:
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
3.3 深入理解useEffect
在上面的例子中,我们发现useEffect
在每次状态更新 组件重新渲染后都会调用
可以通过useEffect第二个可选参数实现
useEffect(() => {
console.log(count)
}, [count]); // 仅在 count 更改时更新
它会对更新之前的count和更新之后的count 做比对,如果发生了改变才去执行effect,否则就会跳过这个effect
注意,如果使用这种方式优化,确保数组中包含了这个useEffect中使用到的所有会随时间变化的变量
那如果我不想每次都重新渲染,比如说加载组件数据的网络请求
可能在原先的class组件上,我只想调用一次就够了,而不是每次状态更新都去重新发送请求
可以给第二个参数传一个空数组
// 只会执行一次了
useEffect(() => {
// ...ajax请求
}, [])
其实原理是一样的,由于传了空数组,下一次更新的空数组等于第一次更新的空数组,所以就会跳过下一次更新
3.4 useContext
使用起来比class的方式方便很多
// 创建一个
const CounterContext = createContext(0)
function Counter() {
return (
<CounterContext.Provider value={count}>
<Bar></Bar>
</CounterContext.Provider>
)
}
// 而且使用多个context也是可以的
function Bar() {
const count = useContext(CounterContext)
return (
<h1>{count}</h1>
)
}
相关文章
官方文档