1、主要功能
- 对函数型组件进行增强,让函数型组件可以储存状态,可以拥有处理副作用的能力
- 让开发者在不使用类组件的情况下,实现相同的功能
- 副作用:只要不是将数据转换为视图的代码都是副作用代码,比如:获取
DOM元素、为DOM元素添加事件、设置定时器、发送ajax请求。在类组件中我们通常使用生命周期函数处理这些副作用,在函数组件中我们通过Hooks处理这些事情。
2、类组件的不足
- 缺少逻辑复用机制 类组件中一般通过渲染属性和高阶组件(HOC)复用一些逻辑,需要在外层嵌套组件,导致层级比较深。
// hoc的定义
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
// 一些通用的逻辑处理
render() {
// ... 并使用新数据渲染被包装的组件!
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
// 使用
const BlogPostWithSubscription = withSubscription(BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id));
【来源:https://wuhou.fun/149.html,转载请注明】
总结为:为了复用逻辑增加了无实际渲染效果的组件,增加了组件的层级显示十分臃肿,增加了调试的难度以及运行的效率。
-
类组件经常会变得很复杂难以维护 将一组相干的业务逻辑拆分到多个生命周期函数中,在一个生命周期函数内存在多个不相干的业务逻辑。
-
类成员方法不能保证
this指向的正确性(通常需要在事件处理函数中改变this的指向)
3、使用useState让函数组件保存状态
-
通常一个函数中的变量,在函数执行完后,变量的内存会被释放掉。因此函数型组件原本是不可以保存数据的。但使用
useState就可以让函数组件保存状态,useState内部是使用闭包保存状态的。当状态值改变后函数组件会重新渲染。 -
useState的使用细节
- 接收唯一的参数即状态初始值. 初始值可以是任意数据类型.
- 返回值为数组. 数组中存储状态值和更改状态值的方法. 方法名称约定以
set开头, 后面加上状态名称. - 方法可以被调用多次. 用以保存不同状态值.
- 参数可以是一个函数, 函数返回什么, 初始状态就是什么, 函数只会被调用一次, 用在初始值是动态值的情况
- 设置状态方法的使用细节
- 设置状态值方法的参数可以是一个值也可以是一个函数
- 设置状态值方法的方法本身是异步的
- 实现原理
- 由于
useState可以被不确定的多次调用,因此需要使用数组储存state变量和设置state的函数数组setters。 - 各个
useState方法需要有自己的下标,因此需要用闭包储存对应的下标 - 由于
setState方法会导致组重新渲染,因此需要在重新渲染方法render中重置state
let state = [];
let setters = [];
let stateIndex = 0;
// 使用闭包储存数组下标,便于点击设置state时拿到正确的下标值
function createSetter (index) {
return function (newState) {
state[index] = newState;
render ();
}
}
function useState (initialState) {
state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
setters.push(createSetter(stateIndex));
let value = state[stateIndex]; // 拿到状态值
let setter = setters[stateIndex];
stateIndex++;
return [value, setter];
}
function render () {
stateIndex = 0; // 重置state,避免页面重新渲染后下标累加
ReactDOM.render(<App />, document.getElementById('root'));
}
4、useReducer() 另一种让函数组件保存状态的方式(类似多个setState)
useReducer相对useState函数的优势:
若App组件的子组件需要修改count,则只需将dispatch传递给子组件去调用action
- 实现原理(基于
useState):实际就是加了reducer函数去判断各种action,触发action用dispathch。
function useReducer (reducer, initialState) {
const [state, setState] = useState(initialState);
function dispatch (action) {
const newState = reducer(state, action);
setState(newState);
}
return [state, dispatch];
}
function App() {
function reducer (state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
const [count, dispatch] = useReducer(reducer, 0);
return <div>
{count}
<button onClick={() => dispatch({type: 'increment'})}>+1</button>
<button onClick={() => dispatch({type: 'decrement'})}>-1</button>
</div>;
5、useContext 用在跨组件层级获取数据时简化获取数据的代码
6、useEffect 让函数型组件拥有处理副作用的能力. 模拟生命周期函数
- 执行时机分析
- 可以把 useEffect 看做 componentDidMount, componentDidUpdate 和 componentWillUnmount 这三个函数的组合
- useEffect(() => {}) =>
componentDidMount,componentDidUpdate - useEffect(() => {}, []) =>
componentDidMount - useEffect(() => () => {}) =>
componentWillUnMount
- 使用方式
- 为window对象添加滚动事件
- 设置定时器让count数值每隔一秒增加1
useEffect钩子函数的第二个参数--只有指定数据发生变化时触发effect
useEffect解决的问题
- 按照用途将代码进行分类 (将一组相干的业务逻辑归置到了同一个副作用函数中)
- 简化重复代码, 使组件内部代码更加清晰
- 结合异步函数
useEffect中的参数函数不能是异步函数, 因为useEffect函数要返回清理资源的函数, 如果是异步函数就变成了返回PromiseUnderstanding React’s useEffect cleanup function
useEffect(() => {
effect
return () => {
cleanup
}
}, [input])
- 实现原理
useEffect同样可以多次调用
function render () {
stateIndex = 0;
effectIndex = 0;
ReactDOM.render(<App />, document.getElementById('root'));
}
// 上一次的依赖值
let prevDepsAry = [];
let effectIndex = 0;
function useEffect(callback, depsAry) {
// 判断callback是不是函数
if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数的第一个参数必须是函数');
// 判断depsAry有没有被传递
if (typeof depsAry === 'undefined') {
// 没有传递
callback();
} else {
// 判断depsAry是不是数组
if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数的第二个参数必须是数组');
// 获取上一次的状态值
let prevDeps = prevDepsAry[effectIndex];
// 将当前的依赖值和上一次的依赖值做对比 如果有变化 调用callback
let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
// 判断值是否有变化
if (hasChanged) {
callback();
}
// 同步依赖值
prevDepsAry[effectIndex] = depsAry;
effectIndex++;
}
}
7、useLayoutEffect()
useEffect在浏览器渲染完成之后执行
useLayoutEffect在浏览器渲染之前执行
所以useLayoutEffect总是比useEffect先执行
useLayoutEffect里面的任务最好是影响了布局视图,但如果无此操作,为了用户体验,优先使用useEffect(优先渲染视图)
8、useMemo()
useMemo的行为类似Vue中的计算属性, 可以监测某个值的变化, 根据变化值计算新值.useMemo会缓存计算结果. 如果监测值没有发生变化, 即使组件重新渲染, 也不会重新计算. 此行为可以有助于避免在每个渲染上进行昂贵的计算。
- 示例:
9、memo()
- 性能优化, 如果本组件中的数据没有发生变化, 阻止组件更新. 类似类组件中的
PureComponent和shouldComponentUpdate(return false组织渲染)
- 计数器案例演示:
10、useCallback()
问题:如8中示例解决了父组件state变化后,子组件虽然没有依赖父组件的state,但是会因父组件重新渲染而渲染的问题。用memo阻止组件的重新渲染。但如果父组件传给子组件方法时,仍会存在因父组件重新渲染,导致重新创建了方法实例。子组件也会跟着渲染。这时,可以使用useCallback()缓存函数,监听setState()方法的变化,
作用:性能优化, 缓存函数, 使组件重新渲染时得到相同的函数实例
11、useRef()
- 作用一:获取 DOM 元素
- 作用二: 保存数据(跨组件周期)
useRef()VSuseState()对比:useState()保存的是状态数据,当数据变化后,会触发组件的重新渲染。而useRef()保存的不是状态数据,即使去更改它保存的数据也不会触发组件的重新渲染。即使组件重新渲染, 保存的数据仍然还在。我通常用于保存在程序运行过程中的一些辅助的手续。
12、forwardRef()
少部分时候我们希望props包含ref,这时候就需要forwardRef
13、自定义Hook
- 自定义
Hook是标准的封装和共享逻辑的方式. - 自定义
Hook是一个函数, 其名称以use开头. - 自定义
Hook其实就是逻辑和内置Hook的组合.
14、react-router-dom 路由提供的钩子函数使用
15(副作用Side-effect)
- 什么是副作用
与药物的副作用类似: 减肥药(拉肚子),头孢(过敏),泰诺(头痛)
副作用与纯函数相反,指一个函数处理了与返回值无关的事情
输入参数一样,而输出结果不确定的情况就是副作用,比如我们发送请求,就不一定能得到相同返回
副作用不全是坏事,很多代码必须得借助副作用才能实现如AJAX,修改dom,甚至是console log,副作用会给系统添加不可控的因素,但我们也不应该想方设法躲避副作用,更应该避免的是错误的代码逻辑和思维理念
- 纯函数( pure function )
- 给一个函数同样的参数,那么这个函数永远返回同样的值
- React组件输入相同的参数(props),渲染UI应该永远一样