You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
importReactfrom'react'varcreateReactClass=require('create-react-class')constmixins={onMouseMove: function(e){this.setState({x: e.clientX,y: e.clientY})}}constMouse=createReactClass({mixins: [mixins],getInitialState: function(){return{x: 0,y: 0}},render(){return(<divonMouseMove={this.onMouseMove}style={{height: '300px'}}><p>the current mouse position is ({this.state.x},{this.state.y})</p></div>)}})
importReactfrom'react'classMouseextendsReact.Component{render(){const{ x, y }=this.props.mousereturn(<p>The current mouse position is ({x}, {y})</p>)}}classCatextendsReact.Component{render(){const{ x, y }=this.props.mousereturn(<divstyle={{position: 'absolute',left: x,top: y,backgroundColor: 'yellow',}}>i am a cat</div>)}}constMouseHoc=(MouseComponent)=>{returnclassextendsReact.Component{constructor(props){super(props)this.state={x: 0,y: 0}}onMouseMove=(e)=>{this.setState({x: e.clientX,y: e.clientY})}render(){return(<divstyle={{height: '300px'}}onMouseMove={this.onMouseMove}><MouseComponentmouse={this.state}/></div>)}}}constWithCat=MouseHoc(Cat)constWithMouse=MouseHoc(Mouse)constMouseTracker=()=>{return(<div><WithCat/><WithMouse/></div>)}exportdefaultMouseTracker
constMouseHoc=(MouseComponent,props)=>{props.text=props.text+'--I can operate props'returnclassextendsReact.Component{render(){return(<divstyle={{height: '100%'}}onMouseMove={this.handleMouseMove}><MouseComponent{...props}mouse={this.state}/></div>)}}MouseHoc(Mouse,{text: 'some thing...'})
通过Refs访问组件
constMouseHoc=(MouseComponent)=>{returnclassextendsReact.Component{
...
render(){constprops={ ...this.props,mouse: this.state}return(<divstyle={{height: '300px'}}onMouseMove={this.onMouseMove}><MouseComponent{...props}/></div>)}}}classMouseextendsReact.Component{componentDidMounted(){this.props.onRef(this)}render(){const{ x, y }=this.props.mousereturn(<p>The current mouse position is ({x}, {y})</p>)}}constWithMouse=MouseHoc(Mouse)classMouseTrackerextendsReact.Component{onRef(WrappedComponent){console.log(WrappedComponent)// Mouse Instance}render(){return(<divstyle={{height: '100%'}}onMouseMove={this.handleMouseMove}><WithMousemouse={this.state}ref={this.onRef}/></div>)}}
classMouseextendsReact.Component{render(props){const{ x, y }=propsreturn(<p>The current mouse position is ({x}, {y})</p>)}}constMouseHoc=(MouseComponent)=>{returnclassextendsMouseComponent{constructor(props){super(props)this.state={x: 0,y: 0}}onMouseMove=(e)=>{this.setState({x: e.clientX,y: e.clientY})}render(){constprops={mouse: this.state}return(<divstyle={{height: '300px'}}onMouseMove={this.onMouseMove}>{super.render(props)}</div>)}}}constWithMouse=MouseHoc(Mouse)
Mixins
React MiXin只能通过React.createClass()来使用,如下:
Mixins实现
Mixins问题
你可能会写一个有状态的组件,然后你的同事可能添加一个读取这个组件
state
的mixin
。几个月之后,你可能希望将该state
移动到父组件,以便与其兄弟组件共享。你会记得更新这个mixin
来读取props
而不是state
吗?如果此时,其它组件也在使用这个mixin
呢?无法保证两个特定的
mixin
可以一起使用。例如,如果FluxListenerMixin
和WindowSizeMixin
都定义来handleChange()
,则不能一起使用它们。同时,你也无法在自己的组件上定义具有此名称的方法。每一个新的需求都使得
mixins
更难理解。随着时间的推移,使用相同mixin
的组件变得越来越多。任何mixin
的新功能都被添加到使用该mixin
的所有组件。没有办法拆分mixin的“更简单”的部分,除非复制代码或者引入更多依赖性和间接性。逐渐,封装的边界被侵蚀,由于很难改变或者删除现有的mixins,它们不断变得更抽象,直到没有人了解它们如何工作。高阶组件
高阶组件是参数为组件,返回值为新组件的函数
组件是将
props
转换为UI
,而高阶组件是将组件转换为另一个组件。HOC的实现
Props Proxy
: HOC对传给WrappedComponent的props进行操作Inheritance Inversion
HOC继承WrappedComponent,官方不推荐Props Proxy
请注意:HOC不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC通过将组件包装在容器组件中来组成新组件。HOC是纯函数,没有副作用。
在Props Proxy模式下,我们可以做什么?
在HOC里面可以对props进行增删改查操作,如下:
Inheritance Inversion
该模式比较少见,一个简单的例子如下:
Inheritance Inversion
允许HOC通过this访问到WrappedComponent,意味着它可以访问到state、props、组件生命周期方法和render方法,HOC可以增删改查WrappedComponent实例的state,这会导致state关系混乱,容易出现bug。要限制HOC读取或者添加state,添加state时应该放在单独的命名空间里,而不是和WrappedComponent的state一起HOC约定
HOC
为组件添加特性。自身不应该大幅改变约定。HOC
返回的组件与原组件应保持类似的接口。HOC
应该透传与自身无关的props
。大多数HOC都应该包含一个类似于下面的render
方法:这中约定保证来
HOC
的灵活性以及可复用性。并不是所有的
HOC
都一样,有时候它仅接受一个参数,也就是被包裹的组件:HOC
通常可以接收多个参数。比如在Relay
中,HOC
额外接收来一个配置对象用于指定组件数据依赖:最常见的
HOC
签名如下:换句话说,
connect
是一个返回高阶组件的高阶函数。这种形式可能看起来令人困惑或者不必要,但是它有一个有用的属性。像
connect
函数返回的单参数HOC�
具有签名Component => Component
。输出类型与输入类型相同的函数很容易组合在一起。HOC
创建的容器组件与任何其他组件一样,会显示在React Developer Tools
中。为了方便调试,请选择一个显示名称,已表明是HOC
的产品。比如高阶组件名为
withSubscription
,被包装组件的显示名称为CommentList
,显示名称应该为WithSubscription(CommentList)
注意事项
render
方法中使用HOC当你将
HOC
应用于组件时,原始组件将使用容器组件进行包装,这意味着新组件没有原始组件的任何静态方法。为了解决这个问题,你可以在返回之前把这些方法拷贝到容器组件上:
但是这样做,你需要知道哪些方法应该被拷贝,你可以使用
hoist-non-react-statics
自动拷贝所有React静态方法:除了导出组件,另一个可行的方案是再额外导出这个静态方法
虽然高阶组件约定是将所有props传递给被包装组件,但对于refs并不适用。因为
ref
实际上并不是一个prop,就像key
一样,它是由React专门处理的。如果将ref添加到HOC的返回组件中,则ref引用指向容器组件,而不是被包装组件。Render Props
具有
render prop
的组件接受一个函数,该函数返回一个React元素并调用它而不是实现自己的渲染逻辑Render Props 实现
render props是一个用于告知组件需要渲染什么内容的函数prop
有趣的是,你可以使用带有 render prop的常规组件来实现大多数高阶组件
HOC
。注意:你不一定要用名为
render
的prop来使用这种模式。事实上,任何被用于告知组件需要渲染什么内容的函数prop在技术上都可以被称为“render prop”。尽管之前的例子使用来
render
,我们可以简单地使用children
prop!记住,
children
prop并不真正需要添加到JSX元素的“attributes”列表中。你可以直接放在元素内部!由于这一技术的特殊性,当你在涉及一个类似的API时,建议在你的propTypes里声明children的类型应为一个函数。
将Render props与React.PureComponent一起使用时要小心
在上述例子中,每次
<MouseTracker>
渲染,它会生成一个新的函数作为<Mouse render>
的prop, 所以同时抵消了继承自React.PureComponent
的<Mouse>
组件的效果。可以定义一个prop作为实例方法:
高阶组件和render props 问题
如果使用HOC或者render props方案来实现组件之间复用状态逻辑,会很容易形成“嵌套地狱”。
随着应用功能的扩大,组件也会变复杂,逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。比如,组件常常在
componentDidMount
和componentDidUpdate
中获取数据。但是,同一个componentDidMount
中可能也包含很多其它逻辑,如设置事件监听,而之后需在componentWillUnmount
中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生bug,并且导致逻辑不一致。测试也难以测试。需要学习class语法,还要理解Javascript中
this
的工作方式,这与其它语言存在巨大差异。还不能忘记绑定事件处理。对于函数组合和class组件的差异也存在分歧,甚至还要区分两种组件的使用场景。使用class组件会无意中鼓励开发者使用一些让优化措施无效的方案。class也给目前的工具带来问题,比如,class不能很好的压缩,并且会使热重载出现不稳定的情况。Hooks
Hook是React 16.8点新增特性,它可以让你在不编写class的情况下使用state以及其它的React特性。
Hooks实现
使用State Hoook
声明多个state变量
调用 useState 方法的时候做了什么?
它定义了一个“state变量”。我们可以叫他任何名称,与class里面的
this.state
提供的功能完全相同。useState 需要哪些参数?
useState()
方法里面唯一的参数就是初始state,可以使用数字或字符串,而不一定是对象。useState 方法的返回值是什么?
返回值为:当前state以及更新state的函数。
使用 Effect Hook
Effect Hook 可以让你在函数组件中执行副作用操作
数据获取,设置订阅以及手动更改React组件中的DOM都属于副作用。
你可以把
useEffect Hook
看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数组合。在React组件中,有两种常见副作用操作:需要清除的和不需要清除的。有时候,我们只想在React更新DOM之后运行一些额外代码。比如发送网络请求,手动变更DOM,记录日志,这些都是常见的无需清除的操作。因为我们在执行完这些操作之后,就可以忽略他们了。
useEffect做了什么?
通过使用这个Hook,你可以告诉React组件需要在渲染后执行某些操作。React会保存你传递的函数,并且在执行DOM更新之后调用它。
为什么在组件内部调用useEffect
将
useEffect
放在组件内部让我们可以在effect中直接访问count
state变量(或其它props)。这里Hook使用了JavaScript的闭包机制。useEffect会在每次渲染后都执行吗?
是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行。
useEffect函数每次渲染中都会有所不同?
是的,这是刻意为之的。事实上这正是我们刻意在effect中获取最新的
count
的值,而不用担心过期的原因。因为每次我们重新渲染,都会生成新的effect,替换掉之前的。某种意义上讲,effect更像是渲染结果的一部分————每个effect“属于”一次特定的渲染。例如订阅外部数据源,这种情况下,清除工作是非常重要的,可以防止引起内存泄漏。
为什么要在effect中返回一个函数?
这是effect可选的清除机制。每个effect都可以返回一个清除函数,如此可以将添加和移除订阅的逻辑放在一起。它们都属于effect的一部分。
React何时清除effect?
React会在组件卸载的时候执行清除操作。effect在每次渲染的时候都会执行,在执行当前effect之前会对上一个effect进行清除。
为什么每次更新的时候都要运行Effect
如下是一个用于显示好友是否在线的
FriendStatus
组件。从class中props读取friend.id
,然后组件挂载后订阅好友的状态,并在卸载组件的时候取消订阅。但是当组件已经现在屏幕上,friend prop发生变化时会发生什么?我们组件将继续展示原来的好友状态,这是一个bug。而且我们还会因为取消订阅时使用错误的好友ID导致内存泄漏或崩溃的问题。
在class组件中,我们需要添加
componentDidUpdate
来解决这个问题。如果使用Hook的话:
它并不会收到此bug影响,因为
useEffect
默认就会处理。它会在调用一个新的effect之前对前一个effect进行清理。具体调用序列如下:通过跳过Effect进行性能优化
在某些情况下,每次渲染后都执行清理或者执行effect可能会导致性能问题。在class组件中,我们可以通过在
componentDidUpdate
中添加对prevProps
或prevState
的比较逻辑解决:对于
useEffect
来说,只需要传递数组作为useEffect
的第二个可选参数即可:如果组件重新渲染时,
count
没有发生改变,则React会跳过这个effect,这样就实现了性能的优化。如果数组中有多个元素,即使只有一个元素发生变化,React也会执行effect。对于有清除操作的effect同样适用:
Hook规则
只在最顶层使用Hook
不要在循环,条件或嵌套函数中调用Hook,这样能确保Hook在每一次渲染中都按照同样的顺序被调用。这让React能够在多次的
useState
和useEffect
调用之间保持hook状态的正确。只在React函数中使用Hook
不要在普通的Javascript函数中调用Hook
自定义Hook
通过自定义Hook,可以将组件逻辑提取到可重用的函数中。
比如,我们有如下组件显示好友的在线状态:
现在假设聊天应用中有一个联系人列表,当用户在线时把名字设置为绿色。我们可以把类似的逻辑复制并粘贴到
FriendListItem
组件中来,但这并不是理想的解决方案:提取自定义Hook
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和Hook都是函数,所以同样也适用这种方式。
自定义Hook是一个函数,其名称以“use”开头,函数内部可以调用其它的Hook.
所以,之前的
FriendStatus
和FriendListItem
组件可以改写成如下:这段代码等价于原来的示例代码吗?
等价,它的工作方式完全一样。自定义Hook是一种自然遵循Hook设计的约定,而不是React的特性
自定义Hook必须以“use”开头吗?
必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部Hook的调用,React将无法自动检查的你的Hook是否违反了Hook的规则。
在两个组件中使用相同的Hook会共享state吗?
不会。每次使用自定义Hook时,其中的所有state和副作用都是完全隔离的。
React Hooks原理
上伪代码:
useState
useEffect
Not Magic, just Arrays
以上代码虽然实现了可以工作的
useState
和useEffect
,但是都只能使用一次。比如:count和usename永远相等,因为他们共用一个_state,所以我们需要可以存储多个_state和_deps。我们可以使用数组来解决Hooks的复用问题。
如果所有_state和_deps存放在一个数组,我们需要有一个指针能标识当前取的是哪一个的值。
到这里,我们就可以实现一个任意复用的
useState
和useEffect
了。参考链接:
React-代码复用(mixin.hoc.render props)
React Hooks 原理
The text was updated successfully, but these errors were encountered: