要将<Bulbs>转换为有状态组件,需要告诉 React:从'react'包中导入useState钩子,然后在组件函数的顶部调用useState()。
在Bulbs函数的第一行调用useState(),在组件内部调用会使该函数成为有状态的函数组件。
启用状态后,下一步是初始化。
useState(false)用false初始化状态。
on状态变量保存状态值。
状态已经启用并初始化,现在可以读取它了。但是如何更新呢?再来看看useState(initialState)返回什么。
useState(initialState)返回一个数组,其中第一项是状态值,第二项是一个更新状态的函数。
状态一旦改变,React 就会重新渲染组件,on变量获取新的状态值。
状态更新作为对提供一些新信息的事件的响应。这些事件包括按钮单击、HTTP 请求完成等,确保在事件回调或其他回调中调用状态更新函数。
setOn(on =>!on)使用函数更新状态。
通过多次调用useState(),一个函数组件可以拥有多个状态。
[on, setOn] = useState(false) 管理开/关状态
[count, setCount] = useState(1)管理数量。
多个状态可以在一个组件中正确工作
每当 React 重新渲染组件时,都会执行useState(initialState)。 如果初始状态是原始值(数字,布尔值等),则不会有性能问题。
当初始状态需要昂贵的性能方面的 *** 作时,可以通过为useState(computeInitialState)提供一个函数来使用状态的延迟初始化,如下所示:
getInitialState()仅在初始渲染时执行一次,以获得初始状态。在以后的组件渲染中,不会再调用getInitialState(),从而跳过昂贵的 *** 作。
在使用useState() 时,必须遵循的规则
1、仅顶层调用:不能在循环,条件,嵌套函数等中调用useState().在多个useState()调用中,渲染之间的调用顺序必须相同。
2、仅从React 函数调用 :必须仅在函数组件或自定义钩子内部调用useState()。
下面看下useState()的正确用法和错误用法的例子。
useState()在函数组件的顶层被正确调用
以相同的顺序正确地调用多个useState()调用:
useState()在自定义钩子的顶层被正确调用
在条件中调用useState()是不正确的
在嵌套函数中调用useState()也是不对的
闭包是一个从外部作用域捕获变量的函数。
闭包(例如事件处理程序,回调)可能会从函数组件作用域中捕获状态变量。 由于状态变量在渲染之间变化,因此闭包应捕获具有最新状态值的变量。否则,如果闭包捕获了过时的状态值,则可能会遇到过时的状态问题。
来看看一个过时的状态是如何表现出来的。组件<DelayedCount>延迟3秒计数按钮点击的次数
快速多次点击按钮。count 变量不能正确记录实际点击次数,有些点击被吃掉。
delay() 是一个过时的闭包,它从初始渲染(使用0初始化时)中捕获了过时的count变量。
为了解决这个问题,使用函数方法来更新count状态:
现在setCount(count =>count + 1)在delay()中正确更新计数状态。React 确保将最新状态值作为参数提供给更新状态函数,过时闭包的问题解决了。
快速单击按钮。 延迟过去后,count 能正确表示点击次数。
useState()用于管理简单状态。对于复杂的状态管理,可以使用useReducer() 。它为需要多个状态 *** 作的状态提供了更好的支持。
假设需要编写一个最喜欢的电影列表。用户可以添加电影,也可以删除已有的电影,实现方式大致如下:
状态列表需要几个 *** 作:添加和删除电影,状态管理细节使组件混乱。
更好的解决方案是将复杂的状态管理提取到reducer中:
reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,而 dispatch 是一个可以发布事件来更新 state 的
在 useReducer 传入 reducer 函数根据 action 来更新 state,如果 action 为 add 正增加 state 也就是增加 count。
在 button 中调用 dispatch 发布 add 事件,发布 add 事件后就会在 reducer 根据其类型对 state 进行对应 *** 作,更新 state。
reducer 管理电影的状态,有两种 *** 作类型:
注意组件功能没有改变。但是这个版本的 <FavoriteMovies>更容易理解,因为状态管理已经被提取到 reducer 中。
还有一个好处:可以将 reducer 提取到一个单独的模块中,并在其他组件中重用它。另外,即使没有组件,也可以对 reducer 进行单元测试。
这就是关注点分离的威力:组件渲染UI并响应事件,而 reducer 执行状态 *** 作。
查看效果: https://codesandbox.io/s/react-usestate-complex-state-usereducer-gpw87
考虑这样一个场景:咱们想要计算组件渲染的次数。
一种简单的实现方法是初始化countRender状态,并在每次渲染时更新它(使用useEffect())
函数组件中没有生命周期,那么可以使用 useEffect 来替代。如果你熟悉 React class 的生命周期函数,你可以把 useEffect 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
useEffect()在每次渲染后调用afterRender()回调。但是一旦countRender状态更新,组件就会重新渲染。这将触发另一个状态更新和另一个重新渲染,依此类推。
可变引用useRef()保存可变数据,这些数据在更改时不会触发重新渲染,使用可变的引用改造一下<CountMyRenders>:
每次渲染组件时,countRenderRef可变引用的值都会使countRenderRef.current ++递增。 重要的是,更改不会触发组件重新渲染。
打开例子: https://codesandbox.io/s/react-usestate-vs-useref-g6qv3?file=/src/index.js
要使函数组件有状态,请在组件的函数体中调用useState()。
useState(initialState)的第一个参数是初始状态。返回的数组有两项:当前状态和状态更新函数。
使用 setState(newState)来更新状态值。 另外,如果需要根据先前的状态更新状态,可以使用回调函数setState(prevState =>newState)。
在单个组件中可以有多个状态:调用多次useState()。
当初始状态开销很大时,延迟初始化很方便。使用计算初始状态的回调调用useState(computeInitialState),并且此回调仅在初始渲染时执行一次。
必须确保使用useState()遵循规则。
当闭包捕获过时的状态变量时,就会出现过时状态的问题。可以通过使用一个回调来更新状态来解决这个问题,这个回调会根据先前的状态来计算新的状态。
使用useState()来管理一个简单的状态。为了处理更复杂的状态,一个更好的的选择是使用useReducer() 。
HOOK是React的新增特性,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。下面主要介绍一下useState和useEffect 的使用。
通过在函数组件里调用它来给组件添加一些内部 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。useState 唯一的参数就是初始 state。这个初始 state 参数只有在第一次渲染的时候会被用到。
使用useState可以声明多个state变量
useEffect (副作用函数)是一个 Effect Hook,给函数组件增加了 *** 作副作用(在 React 组件中进行数据获取、订阅或者手动修改 DOM等)的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API,默认情况下,React 会在每次渲染后调用副作用函数 ,包括第一次渲染的时候。
通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里。
上面实例每次重新渲染都要执行一遍useEffect,如果你在useEffect中使用了useState则会导致无限循环,这样显然是影响性能的。为了处理这个问题,我们可以给useEffect传第二个参数。用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(即第一个参数)。
当我们第二个参数传一个空数组[]时,相当于只在首次渲染的时候执行。
在这里只是简单的介绍了HOOK中useState和useEffect的使用,其实HOOK特性还有很多内容值得我们去学习去探索。
React Hook 对于React来说无疑是一个伟大的特性,它将React从类组件推向了函数组件,从而让人们对于JavaScript的理解不再去可以理解晦涩的JS中的类,以及难以琢磨的this。在《你不知道的JavaScript》上卷中,作者就对JavaScript中的类,继承,面向对象做了一定的解释,总的来说就是,在JavaScript中生搬硬套用面向对象,得不偿失,很容易造成学习和理解负担。
在React16之前没有Hook的时候,必须在类组件去维护组件状态,因此必须理解JS中this的工作机制,并且在给元素绑定事件的时候总是需要绑定this。在组件之间复用状态逻辑比较困难,官方提供的 render props 和 高阶组件 确实很好用,但是整个用起来感觉很重,具体关于对类组件的吐槽可以参考React官网 Hook简介 这部分内容。
当使用React Hook去写React应用后,会发现再也不想用类组件了。。。
官方是这么介绍Hook的: Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
在React中提供了以下几种Hook
示例:
将useState这个钩子从react中导入,在函数组件Login中利用解构赋值的方式声明两个常量 num , setNum 作为 useState(0) 的返回值,useState钩子返回一个数组,第一个参数是我们声明的 state变量 ,第二个参数是一个 方法 ,用来手动改变第一个参数。
语法: const [num, setNum] = useState(0)
useState接受参数为基本类型如数字字符串等,也可以是引用类型对象数组等。用来作为初始值。
关于 useState返回值的第二个参数
示例:
官网这么说:如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount , componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
useEffect接收一个 函数 为第一个参数。
关于useEffect 这个Hook,它的形式有以下几种:
说了这么多那到底什么是 副作用 ?
React官网是这样解释的:数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。不管你知不知道这些 *** 作,或是“副作用”这个名字,应该都在组件中使用过它们。
也就是说,当函数在运行的过程中对外部环境造成影响,或者与外部环境发生交互。比如 *** 作DOM,发起请求,设置订阅这种,都属于副作用。同时有些副作用需要清除,比如订阅解绑,定时器延时器清除,有些不需要清除,比如数据获取, *** 作DOM。
那什么样的函数没有副作用呢?纯函数是不会有副作用的。
在使用useEffect hook的时候可以使用多个Effect分离关注点:
举一个场景来说下:d窗场景。在父组件中点击按钮打开d窗,在d窗内部点击关闭按钮关闭d窗。
此时控制d窗打开与关闭的只能是一个状态isOpen,此时这个isOpen状态就需要在字符组件中共享。
使用isOpen控制d窗的显示与隐藏,在父组件中调用setIsOpen方法,打开d窗,在d窗组件中使用useContext共享的父组件中的方法setIsOpen关闭d窗。
注:父子组件一般不会存在一个文件中,需要将popContext导出再在子组件中导入使用。
官网这样解释: useState 的替代方案。它接收一个形如 (state, action) =>newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
示例:
useReducer第一个参数是一个reducer函数,第二个参数是初始化状态,在reducer函数中根据不同的type对state进行不同的处理。类似于redux中的reducer。
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数( initialValue )。返回的 ref 对象在组件的整个生命周期内保持不变。
这个看来有两种用法:
第一种就是命令式 *** 作DOM元素
第二种是关于其另外一个特性变更 .current 属性不会引发组件重新渲染
使用 myRef.count 来统计组件渲染次数。每一次组件重新渲染的时候,都将返回同一个 myRef 对象,并且, myRef 对象发生变化时,并不会导致组件渲染,这样的特性可以用来处理一些特殊场景下的需求。
当自定义一个myRef对象时,每次组件重新渲染都将返回一个新的对象。
这两个Hook可以用来做优化,比如以下例子,有name和age两个状态,子组件只需要在name发生变化时重新渲染,而在age发生变化时不需要重新渲染。
示例:
useMemo第一个参数接收一个函数第二个参数接收一个数组 useMemo(()=>fn, []) ,数组里面是依赖,只有数组里面的依赖发生变化,函数才会执行。
useCallback(fn, deps) 相当于 useMemo(() =>fn, deps) 。
React 官方也在极力推动Hook的发展,并且近期也有了React准备重写文档,更新后的文档针对Hook的内容肯定会更多,而且Vue在3.0版本出来后也使用了类似于React Hook的机制Composition API,这也是一个趋势。
最近在用webpack4+react16+ts4做自己的一个移动端小博客,功能正在完善中,主要是想学习使用一下React技术栈,期间发现React Hook确实非常好用,因此做一些记录。
博客 github 地址: https://github.com/Mstian/mobile-react-blog
线上地址: http://m.tianleilei.cn (开发中)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)