1.React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。
解决此类问题的方案:
一类:render props 和 高阶组件,需要重新组织你的组件结构。
如果你在 React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。
尽管我们可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。
可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷。
2.组件预编译会带来巨大的潜力
使用 class 组件会无意中鼓励开发者使用一些让优化措施无效的方案,而且class 也给目前的工具带来了一些问题。e.g:class 不能很好的压缩,并且会使热重载出现不稳定的情况。因此,推荐函数式
3.使用 Hook 其中一个目的就是要聚合功能模块,不被生命周期函数分割
比如useState实现值与setState的映射关系
比如effect解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。
函数式组件没有实例的概念,函数通过执行去渲染
//函数组件1.0
const Example = (props) => {
// 你可以在这使用 Hook
return <div />;
}
//函数组件2.0
function MyFunctionalComponent() {
// 你可以在这使用 Hook
return <input />;
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}
render() {
// 这样没用!函数式组件根本就没有实例!
return (
<MyFunctionalComponent ref={this.textInput} />
);
}
}
是 React 16.8 的新增特性
在一些模块中包含了 React Hook 的稳定实现。
Hook 是能让你在函数组件中不编写 class 的情况下使用 state 以及其他的 React 特性。它们名字通常都以 use 开始
规定:
只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中)
useEffect(function persistForm() {
// 将条件判断放置在 effect 中
if (name !== '') {
localStorage.setItem('formData', name);
}
});
React Hooks 都是函数,在函数组件中使用,Hook 可以在函数组件里“钩入” React state 及生命周期等特性的函数。当React渲染函数组件时,组件里的每一行代码就会依次执行,Hooks 也就依次调用执行。
const [state, setState] = useState(initialState);
返回数组:state,以及更新 state 的函数。
在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化。
setState 函数用于更新 state,它接收一个新的 state 值并将组件的一次重新渲染加入队列。
如果 initialState 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用
React 中 setState 什么时候是同步的,什么时候是异步的?
React 控制之外的事件中调用 setState 是同步更新的。比如原生 js 绑定的事 件,setTimeout/setInterval 等。
由 React 控制的事件处理程序,以及生命周期函数调用 setState 不会同步更 新 state 。
在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中回头再说,而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是,有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。
副作用:数据获取,设置订阅以及手动更改 React 组件中的 DOM 。
React 的 class 组件中,render 函数是不应该有任何副作用的,基本上都希望在 React 更新 DOM 之后才执行我们的操作。
副作用操作的分类:需要清除的和不需要清除的。
无需清除的操作:在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志
可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 三个函数的组合。通过使用这个 Hook, React 会保存你传递的函数(即effect),默认情况下,useEffect 在第一次渲染之后和每次更新之后都会执行(等同于同时使用componentDidMount 和componentDidUpdate ),另外也可以可以控制它。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让应用看起来响应更快。
大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
useEffect 放在组件内部可以在 effect 中直接访问 count state 变量(或其他 props)。我们不需要特殊的 API 来读取它 —— 它已经保存在函数作用域中。
对比JavaScript的类中方法没有绑定this,Hook 使用了 JavaScript 的闭包机制,而不用在 JavaScript 已经提供了解决方案的情况下,还引入特定的 React API。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
effect 可选的清除机制:如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它,如此可以将添加和移除订阅的逻辑放在一起。
React 会在执行当前 effect 之前对上一个 effect 进行清除。
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
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);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
为什么 effect 的清除阶段在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。
此默认行为保证了一致性,避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug。
假设我们有一个 ChatAPI 模块,它允许我们订阅好友的在线状态。
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
忘记正确地处理 componentDidUpdate 是 React 应用中常见的 bug 来源。
Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect。
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
每个 effect “属于”一次特定的渲染。
默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候 才执行。内置到了 useEffect 的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
}, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。
https://reactjs.bootcss.com/docs/hooks-custom.html
流行的方式来共享组件之间的状态逻辑: render props 和高阶组件
想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和 Hook 都是函数,所以也同样适用这种方式。
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和 Hook 都是函数,所以也同样用这种方式可以将组件逻辑提取到可重用的函数中。
自定义 Hook,自定义 Hook 的名字应该始终以 use 开头。
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
在两个组件中使用相同的 Hook 不会共享 state
自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。
从 React 的角度来看,我们的组件只是多次调用 useState 和 useEffect,它们是完全独立的。
const MyContext = React.createContext(往往是一个对象);
<ThemeContext.Provider value={赋值属性的对象}> { 可能会想拿到context的DOM } </ThemeContext.Provider>
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的
当组件上层最近的
全局数据管理方案:React.useReducer(管理数据)+React.useContext(跨层级通信)
相比useState,更好支持了 设置数据值的 方法隐射,适用于更复杂的数据处理场景,
useState:对数据的处理是使用方自己掌控
useReducer:useReducer自己定义 对数据的处理(Reducer),使用方通过传参一个type值来调用对应的方法
state:只读(所有修改都要通过action)
DomainData服务器响应数据、网络请求 UI APP级别action:一个具有type属性和其他属性的js对象
reducer:接收初始化state和action的函数,响应不同类型的action,修改并返回state 发送给store
action文件夹下面是,action的构造函数sendAction,有无传参?
reducer文件夹下面是,reducer
Object.assign({},state,action)
store文件夹
import { createStore } from redux
store=createStore(reducer);
组件加载完毕,我们通过store来进行监听器的注册,返回值可以用来注销监听
store.subscribe(()=>{…可以拿到store.getState});//注册监听后,可以拿到reducer处理之后的返回值state
点击事件处理函数中,通过store.dispatch发送action
导入store之后都可以通过store.getState()获取到state对象,触发发送action之前,state对象是reducer里面的默认值
应用的单一状态树
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。(React 使用 Object.is 比较算法 来比较 state。)
state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数setState 。
如果 Reducer Hook 的返回值与当前 state 相同,React 将跳过子组件的渲染及副作用的执行。(React 使用 Object.is 比较算法 来比较 state。)
需要注意的是,React 可能仍需要在跳过渲染前再次渲染该组件。不过由于 React 不会对组件树的“深层”节点进行不必要的渲染,所以大可不必担心。如果你在渲染期间执行了高开销的计算,则可以使用 useMemo 来进行优化。
const [store, dispatch] = useReducer(reducer, ‘blue’)
在
这时候dispatch成为context,后面只需要通过const { dispatch } = useContext(Context);解构获得引用
传入空数组的写法 ,直接采用useState初始化, 是不会在每次重新渲染时都重新初始化的,只有在组件重载时才会初始化。
useMemo(计算函数,依赖项数组)
可以让函数在某个依赖项改变的时候才运行
返回一个 memoized 值,仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
计算函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
const options = useMemo(
() => [
{
label: 'false',
value: 1,
data: false,
},
{
label: 'true',
value: 2,
data: true,
},
{
label: '空对象',
value: 3,
data: {},
},
{
label: 'layout: vertical',
value: 4,
data: {
layout: 'vertical',
},
},
{
label: '自定义渲染',
value: 5,
data: {
render: instantRender,
renderContent: instantRenderContent,
},
},
{
label: '带toast提示',
value: 6,
data: {
onConfirm() {
toast.success('提交成功');
},
onCancel() {
toast.warning('取消了提交');
},
},
},
],
[],
);
因为函数式组件中的state的变化都会导致整个组件被重新刷新(依赖state的函数和计算值),useCallback、useMemo和useEffect可以设置只有在依赖数据发生变化后,才会重新计算结果,起到缓存的作用。
不使用memo和useMemo不应该会导致你的业务逻辑发生变化(memo和useMemo只是用来做性能优化),类似于类组件中的 shouldComponentUpdate
如果该函数或变量作为 props 传给子组件,请一定要用,避免子组件的非必要渲染
第二个参数是用于触发-检测上下文中对应值是否变化,如果有变化则会重新声明回调函数。如果这个参数为空数组,则只会在component挂载即componentDidMount运行。如果不存在这个参数,则会在每次渲染时运行。
useMemo和useEffect的执行时机有区别:
useMemo是需要返回值,返回的是计算的结果值,用于缓存计算后的状态。返回值直接参与渲染的,所以useMemo是在渲染期间完成的
useEffect执行的是副作用,所以在渲染之后执行的。
因为 onClick 使用了 inline 函数,所以 PureComponent 默认的浅比较也同样失去了意义。
useCallback 的真正目的还是在于缓存了每次渲染时 inline callback 的实例,这样方便配合上子组件的 shouldComponentUpdate 或者 React.memo 起到减少不必要的渲染的作用。需要不断提醒自己注意的是,在大部分 callback 都会是 inline callback 的未来,React.memo 和 React.useCallback 一定记得需要配对使用,缺了一个都可能导致性能不升反“降”,毕竟无意义的浅比较也是要消耗那么一点点点的性能。
why:每次渲染组建时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。
返回一个 memoized 回调函数
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。
const handleInstantEditingChange = useCallback(
(value: any) => {
const targetOption: any = options.find(o => o.value === value);
if (targetOption) {
setInstantEditing(targetOption.data);
}
},
[options],
);
memo用来优化函数组件的重复渲染行为,当传入属性值都不变的情况下不会触发组件的重新渲染,否则就会触发组件的重新渲染;和类组件的PureComponent的功能是类似的
在hooks环境下,几乎所有组件都是函数式组件,我们使用memo的几率要比PureComponent高得多;
在使用中遇到一个问题:
父、子、孙三个组件,父有一个x属性,父传给子x,子直接把props.x传递给孙,孙只会接收到初值,深拷贝?
是浅拷贝,动态接受x的变化
只是我对函数使用了useCallback却没有注册任何依赖项,相当于后面调用经过useCallback处理的函数时,除了调用时候传入的参数之外,函数的作用域是之前缓存的,相当于使用的是一开始的值。