内容简介:hooks是react16.8版本中新增的特性,它让我们能够在不写class的情况下使用状态和其他react特性。也就是说现在我们可以在函数组件中进行状态管理了。在hooks之前解决这个问题的手段是render props和高阶组件。但是这些方法都需要我们去修改组件层级关系,让代码变得很繁琐。hooks可以在不改变组件层级的前提下将带有状态的逻辑抽离出来。
hooks是什么
hooks是react16.8版本中新增的特性,它让我们能够在不写class的情况下使用状态和其他react特性。也就是说现在我们可以在函数组件中进行状态管理了。
hooks解决了什么问题
- 组件中带状态的逻辑很难复用
在hooks之前解决这个问题的手段是render props和高阶组件。但是这些方法都需要我们去修改组件层级关系,让代码变得很繁琐。
hooks可以在不改变组件层级的前提下将带有状态的逻辑抽离出来。
- 复杂组件让人难以理解
原先用class写的组件集成了许多生命周期函数,这些生命周期函数中又包含了许多互不相关的逻辑,比如接口请求,事件绑定等等。这导致组件逻辑复杂之后难以理解也难以测试。
hooks可以将复杂组件的逻辑拆分成更小的函数,这些函数只负责单一的逻辑。拆分后的优点是易懂易测试。
- class本身存在的问题
尽管class已经是一个语法糖了,但是react的开发者认为this是一个很麻烦的东西,我们在用class写组件前必须先搞清楚this的工作原理。另外class组件在react编译过程中也存在一些问题,如压缩率并不是很好,热加载不稳定等等。
hooks可以让我们在函数组件中管理状态。尽管完全抛弃class组件还为时尚早,但是有了hooks我们使用class组件的机会将越来越少。
几个常用的hooks
useState
基本用法:
import React, { useState } from 'react';
export default function Foo() {
const [selectedKey, setSelectedKey] = useState('');
const someHandler = () => { setSelectedKey('1') };
...
return ...;
}
上面例子中,useState函数会返回一个数组,数组第一项是我们定义的一个状态selectedKey,第二项是修改这个状态的函数,而userState接收的参数就是这个状态的初始值。当我们使用setSelectedKey修改状态时,react会重新渲染该组件,效果跟setState一样。
这段代码改成class的写法是这样的:
import React, { Component } from 'react';
export default class Foo extends Component {
state = {
selectedKey: ''
}
someHandler = () => {
this.setState({selectedKey: '1'});
}
...
render() {
return ...;
}
}
区别:
- 使用class时,我们把组件的所有状态都放在state这个对象中;而一个useState只定义一个状态量,组件有几个状态变量就写几行useState
- setState是将新状态merge到老状态中,而useState返回的函数setSelectedKey是将新状态替换老状态,因为一个useState只定义一个状态量所以这边直接替换是没有问题的
useEffect
顾名思义,useEffect就是去做一些有副作用的事。默认情况下useEffect接收一个函数作为参数,在每次render结束后react会去执行这个函数,效果相当于componentDidMount和componentDidUpdate的组合。
下面这段代码表示每次渲染后将状态selectedKey的值设为home:
import React, { useState, useEffect } from 'react';
export default function Foo() {
const [selectedKey, setSelectedKey] = useState('');
useEffect(()=> {
setSelectedKey('home');
});
...
return ...;
}
useEffect的功能还不止这么简单,当我们进行某些副作用操作后,往往需要在组件卸载前做一些清理工作,比如清除定时器,解绑事件监听器等等。因此react在useEffect中增加了一个特性,允许传入的函数再返回一个函数,这个返回函数的执行时机是下一次触发这个useEffect前,以及组件卸载前。
如果我们只想在组件卸载前进行一些清理工作,那就要用到uesEffect的第二个参数了。第二个参数是一个数组,里面可以放这个副作用的依赖,作用是控制这个副作用执行的时机,只有当依赖发生变化的时候才会执行这个副作用。当第二个参数为空数组时,返回函数(进行清理工作)只会在组件卸载时执行。
下面这个例子的作用是在组件首次渲染后以及props.source的值发生变化后执行subscribe( ),在下一次执行subscribe( )前以及组件卸载前执行unsubscribe( ):
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source]
);
使用useEffect可以模拟react的某些生命周期函数
模拟componentDidMount
useEffect(() => {
// 这里在mount时执行一次
}, []);
模拟componentWillUnmount
useEffect(() => {
// 这里在mount时执行一次
return () => {
// 这里在unmount时执行一次
}
}, []);
模拟componentDidUpdate
// useRef会返回一个对象,这个对象有个current属性,值为传给useRef的参数
// useRef在组件生命周期中只初始化一次,之后它会帮我们保存返回的对象
const mounting = useRef(true);
useEffect(() => {
if (mounting.current) {
mounting.current = false; // 对current的修改会被useRef保存,但修改不会引起重新渲染
} else {
// 这里只在update时执行
}
});
举例
需求:使用antd的menu组件实现一个侧边栏,类似下面的样子,当用户输入不同url时侧边栏需要联动
实现:
import React, { useState, useEffect } from 'react';
import { Menu } from 'antd';
export default function Sider() {
const [selectedKey, setSelectedKey] = useState('');
const [openKeys, setOpenKeys] = useState([]);
// 组件更新时根据url更新选中的菜单项
useEffect(() => {
const key = getSelectedKey(); // 根据url得到选中的菜单项
setSelectedKey(key);
});
// 组件mount时根据url自动展开子菜单
useEffect(() => {
const key = getOpenKey(); // 根据url得到应该展开的菜单
setOpenKeys([key]);
}, []);
...
return (
<Menu
mode="inline"
selectedKeys={[selectedKey]}
openKeys={openKeys}
onOpenChange={openKeys => setOpenKeys(openKeys)}
>
...
</Menu>
);
}
分析:
- 组件使用useState定义了两个状态量:selectedKey和openKeys;
- 第一个useEffect用于更新selectedKey,它会在每次render后从url中获取当前选中的菜单项,然后更新selectedKey;
- 第二个useEffect用于首次进入网站时,从url中获取应该展开的菜单并更新openKeys,它只在组件创建时执行,相当于componentDidMount;
- 当用户点击父菜单想要展开或收起时,通过onOpenChange事件来触发openKeys的更新;
- 当用户点击子菜单想要选中时,会先触发路由跳转(这个逻辑无法从代码中获取,请自行脑补),路由改变会引发改组件重新渲染,继而触发第一个useEffect来更新selectedKey。
自定义hooks
文章开头已经讲到了hooks可以很方便的实现带状态的逻辑复用。
下面是一个简单的自定义hooks,功能是请求接口并返回数据:
import { useState, useEffect } from 'react';
export default function useUserInfo() {
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
fetch('https://react.hooks.com/api/userinfo').then(
data => {
setUserInfo(data);
},
);
}, []);
return userInfo;
}
使用也很简单,当Home组件加载时会通过useUserInfo这个自定义hooks去请求接口,接口数据返回时Home组件会自动更新:
import React from 'react';
import useUserInfo from '../../hooks/useUserInfo';
export default function Home() {
const userInfo = useUserInfo();
return <div>{userInfo}</div>;
}
hooks + context进行全局状态管理
react提供了useContext这个hooks使得在函数组件中使用context变得更加方便。
如果项目没有复杂到需要上redux,可以使用下面的方法进行全局状态管理。
首先创建一个context:
// globalContext.js
import React from 'react';
export default React.createContext({
musicianPlan: '1',
language: 'zh',
changeMusicianPlan: () => {},
changeLanguage: () => {},
});
然后定义一个高阶组件,用于管理context中的状态:
// globalState.jsx
import React, { useState } from 'react';
import GlobalContext from './globalContext';
export default function GlobalState(props) {
const [musicianPlan, setMusicianPlan] = useState('1');
const [language, setLanguage] = useState('zh');
const changeMusicianPlan = planId => {
setMusicianPlan(planId);
};
const changeLanguage = lang => {
setLanguage(lang);
};
return (
<GlobalContext.Provider
value={{
musicianPlan: musicianPlan,
language: language,
changeMusicianPlan: changeMusicianPlan,
changeLanguage: changeLanguage,
}}
>
{props.children}
</GlobalContext.Provider>
);
}
将这个高阶组件放到组件树的顶层:
// app.jsx
import React from 'react';
import GlobalState from './context/globalState';
export default function App() {
return (
<GlobalState>
<Layout>
...
</Layout>
</GlobalState>
);
}
在Header组件中用useContext这个hooks获取到context,然后调用changeMusicianPlan方法来改变全局状态musicianPlan:
import React, { useContext, ReactElement } from 'react';
import { Select } from 'antd';
import GlobalContext from '../../context/globalContext';
const Option = Select.Option;
export default function Header() {
const globalContext = useContext(GlobalContext);
return (
<Select
defaultValue="1"
onChange={value => globalContext.changeMusicianPlan(value)}
>
<Option value="1">计划一</Option>
<Option value="2">计划二</Option>
<Option value="3">计划三</Option>
</Select>
);
}
在Home组件中同样使用useContext获取context,然后使用全局状态musicianPlan进行动态渲染:
import React, { useContext } from 'react';
import GlobalContext from '../../context/globalContext';
export default function Home() {
const globalContext = useContext(GlobalContext);
return <div>{globalContext.musicianPlan}</div>;
}
当然上面的方法也可以用于某个局部组件树的状态管理,将状态进行拆分管理不仅提高运行效率也更清晰易懂。
使用hooks的注意事项
- hooks只能在组件内部的最顶层调用,不能将其放在循环语句、条件语句或者子函数内;
- hooks只能在函数组件或自定义hooks中使用;
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Hacking Growth
Sean Ellis、Morgan Brown / Crown Business / 2017-4-25 / USD 29.00
The definitive playbook by the pioneers of Growth Hacking, one of the hottest business methodologies in Silicon Valley and beyond. It seems hard to believe today, but there was a time when Airbnb w......一起来看看 《Hacking Growth》 这本书的介绍吧!
SHA 加密
SHA 加密工具
HEX CMYK 转换工具
HEX CMYK 互转工具