高阶函数
一个函数执行的返回结果还是一个函数时,它就是一个高阶函数,如下
function debounce (fn, delay = 500, context) {
let timer = null
return function (...args) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(context, args)
timer = null
}, delay)
}
}
防抖函数执行后,返回一个新的函数,新函数内部通过闭包获取实参及变量timer
高阶组件(HOC)
高阶组件(HOC )是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
函数执行后返回的是一个组件,该函数就是一个高阶组件,如下:
function logProps (WrappedComponent) {
return class extends React.Component {
componentDidUpdate(oldProps) {
console.log('oldProps: ', oldProps)
console.log('newProps: ', this.props)
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
该函数的作用是,为一个组件打印props (通过注册componentDidUpdate 钩子函数,在组件活动周期,父级传递的props 变化重新渲染组件,会执行该钩子函数),功能并不实用,仅举例子
使用HOC 可以很好地解决横切关注点问题
示例
官网链接:https://react.docschina.org/docs/higher-order-components.html
使用高阶组件根据横切关注点分离代码,优化后如下:
class CommontList extends React.Component {
render() {
return (
<ul>
{this.props.data.map(commont => (<li key={commont}>{commont}</li>))}
</ul>
)
}
}
class BlogPost extends React.Component {
render() {
return (
<ul>
{this.props.data.text}
</ul>
)
}
}
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props)
this.handleDataChange = this.handleDataChange.bind(this)
this.state = {
data: selectData(DataSource, this.props)
}
}
componentDidMount() {
DataSource.addChangeListener(this.handleDataChange)
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleDataChange)
}
handleDataChange() {
console.log(`${WrappedComponent.name ? WrappedComponent.name : 'Component' } change!`)
this.setState({
data: selectData(DataSource, this.props)
})
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
const SubscribeBlogPost = withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id))
const SubscribeCommontList = withSubscription(
CommontList,
DataSource => DataSource.getComments())
export default class Home extends React.Component {
state = {
blogId: 1,
commontInput: '',
}
componentDidMount () {
}
handleCommontInput(e) {
this.setState({
commontInput: e.target.value
})
}
addComment() {
DataSource.commonts = [...DataSource.getComments(), this.state.commontInput]
this.setState({
commontInput: ''
})
}
render () {
return (
<div>
<h1>Home</h1>
<div>
<input value={this.state.commontInput} onInput={(e) => this.handleCommontInput(e)} />
<button onClick={() => this.addComment()}>添加评论</button>
</div>
<SubscribeCommontList />
<hr />
<SubscribeBlogPost id={this.state.blogId} />
</div>
)
}
}
解释如下:
DataSource 是全局的数据源,一方面提供组件需要的数据,另一方面在数据变化后,执行通过DataSource.addChangeListener() 方法绑定的订阅更新函数,在数据变化时,DataSource 内部会自动调用所有的监听函数;- 定义了一个
CommentList 组件和一个BlogPost 组件,它们都会接收一个data 属性(this.props.data )而该属性的数据类型,则完全由高阶组件withSubscription 的第二个参数selectData 函数确定; - 高阶组件
withSubscription 内部初始化会调用一次selectData() 获取数据,当数据源DataSource 变化后,DataSource trigger 所有的监听函数,也就是handleDataChange() 方法,setState({ data: selectData(DataSource, this.props}),更新了高阶组件的状态后,其被包裹的组件也会刷新
使用建议
- 高阶组件应当是一个纯函数,不要掺杂其他副作用
- 不要改变原始组件,使用组合
- 高阶组件的分离横切关注点应该颗粒化更好,以方便组件的复用和维护
- 更不要修改原始传入的组件特性,应当使用组合包装,例如:在上文【订阅】高阶组件中继续添加打印
props 的功能
function logProps (WrappedComponent) {
return class extends React.Component {
componentDidUpdate(oldProps) {
console.log('oldProps: ', oldProps)
console.log('newProps: ', this.props)
}
render() {
return <WrappedComponent {...this.props} />
}
}
}
const LogPropsSubscribeBlogPost = logProps(withSubscription(
BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id)))
const LogPropsSubscribeCommontList = logProps(withSubscription(
CommontList,
DataSource => DataSource.getComments()))
- 上例需要打印
props 是新的需求,虽然也可以在withSubscription 高阶组件内直接修改,毕竟不冲突,但是如果那样加了之后,**打印props **就和【订阅】 功能绑定在一起了,所以最好分开; - 方便复用高阶组件,没有修改原组件
|