React Hooks 是 React 16.8 版本的新增特性,那么为什么会有 Hooks 产生?任何一项新的技术总是为了解决当前开发中的某一些痛点,而不是无故地重复造轮子,下面我们从两个场景来进行一定的讲解,本文中所有代码均可在 react-hooks-demo 查看。
场景
API 请求
在实际开发过程中,我们有这样一个需求,页面请求一个详情接口,并将 UI 渲染出来:
class GithubProfile extends React.PureComponent<IProps, IStates> {
state: IStates = {
profile: {}
}
componentDidMount() {
fetch('https://api.github.com/users/gaearon')
.then(response => {
return response.json()
})
.then(res => {
this.setState({
profile: res
})
})
}
render() {
const { profile } = this.state
return (
<div className="profile">
<img src={profile.avatar_url} alt="avatar" width="200px" />
<div>name: {profile.name}</div>
<div>company: {profile.company}</div>
<div>bio: {profile.bio}</div>
</div>
)
}
}
我们可以得到这样的结果:
// withGithubProfile
const withGithubProfile = (WrappedComponent:any) => {
return class extends React.Component<IProps, IStates> {
constructor(props:IProps) {
super(props)
this.state = {
profile: {}
}
}
componentDidMount() {
fetch('https://api.github.com/users/gaearon')
.then(response => {
return response.json()
})
.then(res => {
this.setState({
profile: res
})
})
}
render() {
const { profile } = this.state
return <WrappedComponent profile={profile} {...this.props} />
}
}
}
引入高阶组件,使用其 profile Props:
class GithubProfileHoc extends React.Component<IProps, IStates> {
render() {
const { profile } = this.props
return (
<div className="profile">
<img src={profile.avatar_url} alt="avatar" width="200px" />
<div>name: {profile.name}</div>
<div>followers: {profile.followers}</div>
<div>following: {profile.following}</div>
</div>
)
}
}
export default WithGithubProfile(GithubProfileHoc)
然后使用 Render Props 实现:
// Render Props
class Profile extends React.Component<IProps, IStates> {
constructor(props:IProps) {
super(props)
this.state = {
profile: {}
}
}
componentDidMount() {
fetch('https://api.github.com/users/gaearon')
.then(response => {
return response.json()
})
.then(res => {
this.setState({
profile: res
})
})
}
render() {
const { profile } = this.state
return <React.Fragment>{this.props.children(profile)}</React.Fragment>
}
}
定义 props 渲染函数:
class ProfileRenderProps extends React.PureComponent {
render() {
return (
<Profile>
{(profile:any) => (
<div className="profile">
<img src={profile.avatar_url} alt="avatar" width="200px" />
<div>name: {profile.name}</div>
<div>company: {profile.company}</div>
<div>bio: {profile.bio}</div>
</div>
)}
</Profile>
)
}
}
以上两种方式的确可以实现代码复用,但这两种方式也存在一定的缺点: HOC:
- 使用多个高阶组件时,无法确定 props 来源
- 相同的 props 会存在覆盖的情况
- 增加调试难度
Render Props
- 地狱回调
除此之外,这两种方式都应引入了复杂的编程模式,在代码维护以及理解方面不是很友好。
input 输入
紧接着我们看另外一个场景,有一个 input 框,onChange 时获取输入框的值,使用 Class 实现如下:
class Input extends React.Component<IProps, IStates> {
state: IStates = {
name: ''
}
handleOnchange = (e:any) => {
this.setState({
name: e.target.value
})
}
render() {
const { name } = this.state
return (
<div>
<p>input name: {name}</p>
<input value={name} onChange={this.handleOnchange} />
</div>
)
}
}
我们又继续扩展,如果还有一个类似的 input 框,那么怎么实现呢?代码复制还是使用 Hooks 呢 😊
why hooks
以上两种场景或者类似场景在开发过程中比较比较常见,我们有没有更好的方式来实现代码复用。我们很容易想到使用函数来实现代码复用,例如在项目中我们会将一公共的方法抽离出来,例如封装 api 请求的 request.js。有同学可能就会有疑问,React 不是提供了 function 组件?React 的确提供了 function 组件,但在 React 16.8 之前 function 有两个问题:
- function 组件不得不返回一些 UI 信息,即 JSX 代码
- function 组件内部不能拥有 state
正是由于这些问题,React 团队提出了 Hooks 思想,并在 React 16.8 中新增了 React Hooks 特性。说了这么久,那么到底什么是 React Hooks?简单总结一下有以下两点:
- Hooks 让函数式组件拥有类组件一样的功能,state ,lifecycle 以及 context。
- Hooks 不是 React 的新功能,可以将它理解为一个“钩子”,可以让你在不写类组件的情况下“勾住”React 的所有功能。
使用 React Hooks 实现
这篇文章不会讲解 hooks 具体内容,详细内容可在React Hooks 讲解中查看。
usePropfile
使用 Hooks 实现 API 请求:useProfile
// useProfile
const useProfile = () => {
const [profile, setProfile] = useState({} as TProfile)
const [loading, setLoading] = useState(false)
const [isError, setIsError] = useState(false)
useEffect(() => {
setLoading(true)
fetch('https://api.github.com/users/gaearon')
.then(response => {
return response.json()
})
.then(res => {
setProfile(res as TProfile)
setIsError(false)
setLoading(false)
}).catch(()=> {
setIsError(true)
setLoading(false)
})
}, [])
return { profile, loading,isError }
}
使用 usePropfile Hooks:
const UseProfilePage = () => {
const { profile, loading, isError } = useProfile()
return (
<React.Fragment>
{isError ? (
<div>Network Error...</div>
) : (
<div className="profile">
{loading ? (
<div>loading profile...</div>
) : (
<React.Fragment>
<img src={profile.avatar_url} alt="avatar" width="200px" />
<div>name: {profile.name}</div>
<div>company: {profile.company}</div>
<div>bio: {profile.bio}</div>
</React.Fragment>
)}
</div>
)}
</React.Fragment>
)
}
usePropfile hooks 返回了请求结果 profile,请求状态 loading,以及网络状态 isError。然后以函数调用的方式使用 Hooks,这样代码结构看起来很清晰,后期维护也很方便。
useInput
继续使用 Hooks 实现 input 输入逻辑:useInput
const useInput = (initialValue:string) => {
const [value, setValue] = useState(initialValue)
const handleChange = (e:any) => {
setValue(e.target.value)
}
return {
value,
onChange: handleChange
}
}
引入 useInput:
const useInputDemo = () => {
const value = useInput('KuangPF')
return (
<div className="use-input">
<p>current name: {value.value}</p>
<input {...value} />
</div>
)
}
通过以上两个 Hooks可以看出,React Hooks 功能的确强大,在逻辑复用方面比 HOC 以及 Render Props 更具优势,在代码维护上也更加清晰。