函数组件
在v16.8 版本之前,react组件的标准写法是class(类)
,而在版本之后,react引入了一种全新的api,叫做 React Hooks
,它颠覆了以前的用法。
import ReactDOM from 'react-dom';
import React,{Component} from 'react'
class ComponentA extends Component{
constructor(props){
super(props)
this.state={
id:0
}
this.changeBind = this.changeBind.bind(this)
}
changeBind(){
this.setState({
id:this.state.id + 1
})
}
render(){
let id = this.state.id
return(<div>
<button onClick={()=>this.changeBind()}>修改id</button>
<ComponentB id={id}></ComponentB>
</div>)
}
}
ReactDOM.render(<ComponentA />,document.getElementById('root'))
在react固有class(类)写法的影响下,一个普通的组件基本会具备其上的内容,大部分人看来,仅这一个简单组件的代码量都很重,更不用讲一个完成的react项目多层级嵌套了。
为了解决这个问题,其实react团队在很早之前就已经支持函数组件了,但是放在现在的我们,函数组件的用法显然在项目中会受到很大的限制,它没有生命周期,也无法管理状态
function ComponentB(props){
let id = props.id
function changeID(){
id += 10;
console.log('ComponentB:',id)
}
return(<h3>{id}<button onClick={()=>changeID()}>修改本组件的id</button></h3>)
}
如上一例,最开始的id依赖于父组件,当点击父组件的按钮时,id发生变化,组件ComponentB视图中的id也随之发生了变化,但在函数组件内部,我定义了方法,当点击ComponentB中的按钮时,虽然触发了事件,但页面视图却没有发生改变,这就是没有状态管理的函数组件的局限。
什么是Hook?
为了解决以上问题,react提供了react hook,可以说它是一种专门辅助函数组件的工具,本身也是一个特殊的函数,它可以让你“钩入” React 的特性。
它提供了四钟常用的钩子
useState()
useContext()
useEffect()
1、useState()
import ReactDOM from 'react-dom';
import React,{Component,useState} from 'react'
class ComponentA extends Component{...}
function ComponentB(props){
let [id,changeID]=useState(props.id)
return(<h3>{id}<button onClick={()=>changeID(id+10)}>修改本组件的id</button></h3>)
}
ReactDOM.render(<ComponentA />,document.getElementById('root'))
我们在组件中引入useState,并将ComponentB稍作修改,现在的我们就可以单独控制ComponentB中的id了,当我们点击按钮,页面视图中的id也随之发生改变。
由于纯函数组件中不能有状态,所以它把状态放在了状态钩子useState中
解读 let [id,changeID]=useState(props.id)
在使用钩子的时候,它会返回一个数组,数组中固有两个参数,一个是我们状态控制的变量 = 》id,另一个是用来控制状态的函数=》changeID,而我们在使用状态钩子的时候传入了一个初始值 = 》props.id,这个初始值将赋值给状态控制的变量=》id。
也就是说,这个状态钩子帮我们做了两件事,一是声明了一个需要被控制的字段,并给了它一个初始值,二是定义了一个可修改状态的公用方法,并将其封装进了我们声明的函数方法中。
注意:当前组件ComponentB中的id是重新定义的,当我们修改了ComponentB中的id后,父组件中的id并不受其影响,且后续若父组件的id发生了改变,ComponentB的id也不受其影响。
2、useContext()与createContext()
从上边的例子我们知道,useState只适用单个函数组件的状态控制,如果这时候我们有两个函数组件公用同一个变量,且当该变量发生改变时两个函数组件都会受其影响,这时候,我们就需要使用到 共享状态钩子useContext()
为了适用我们当前内容,下面以新的实例进行说明:
import ReactDOM from 'react-dom';
import React,{createContext,useContext} from 'react'
const CommonContext = createContext({});
class App extends React.Component{
constructor(props){
super(props)
this.state = {
id:0
}
}
render(){
let id = this.state.id
return(
<div>
父组件:<button onClick={()=>{this.setState({id:id + 1})}}>修改id</button>
<CommonContext.Provider value={{id:id}}>
<ComponentA />
<ComponentB />
</CommonContext.Provider>
</div>
)
}
}
function ComponentA(props) {
let {id} = useContext(CommonContext)
return(<div>子组件A:{id}</div>)
}
function ComponentB(props) {
let {id} = useContext(CommonContext)
return(<div>子组件B:{id}</div>)
}
ReactDOM.render(<App />,document.getElementById('root'))
当前实例中,如果你点击了父组件中的按钮,对id进行修改,两个子组件A、B中依赖的id都会随之发生改变。
初次接触hook,可能看到代码中的 CommonContext.Provider
会有疑惑,不要着急,马上为你解惑:
通俗点说,你修好了一套房,取名叫CommonContext,Provider就是房子的墙壁,它区分了房子的内外,只有在房子里的人才能使用空调,洗衣机,电视机,房子外就没办法使用,当然,也要你先有空调、电视、洗衣机才有得用,value就是你要放这些家电的空间,在value中就是你要放置的家电。
需要共享某个参数的组件都需要放置在 CommonContext.Provider
组件内,只有放置在这个组件内的组件才可以共享它的参数,value中可以定义你要共享的字段,在子函数组件中,引入一下共享钩子(useContext(CommonContext)
)就可以使用你定义的参数了。
点击父组件的按钮,两个子组件所使用的id都会受其影响;
3、useEffect 副作用钩子
它跟 class 组件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,当你在 React 组件中执行数据获取、订阅或者手动修改 DOM ,就会触发副作用钩子,react 也将这些操作称作 “副作用”。
以下我们继续使用新的实例进行说明:
import ReactDOM from 'react-dom';
import React,{useState,useEffect } from 'react'
function App() {
const [id,setId] = useState(0)
useEffect(()=>{
alert('触发了副作用钩子')
})
return (
<div>ID:{id}<button onClick={()=>setId(id+1)}>修改id</button></div>
)
}
ReactDOM.render(<App />,document.getElementById('root'))
以上是一个简单的副作用钩子使用方法,默认情况下,每一次渲染都会触发副作用钩子,包括第一次
因为刷新页面的时候组件进行了第一次渲染,而点击按钮的时候又对数据进行了操作,所以都触发了副作用钩子
副作用钩子还可以在组件中多次使用。
通过跳过 Effect 的 useEffect 第二个参数
综上实例,我们知道了Effect的第一个参数是一个函数,当每次进行副作用操作之后,Effect都会执行这个函数,而在某些情况下,每次渲染后都执行副作用钩子可能会导致性能问题,这时候就有了Effect的第二个参数,它规定了只有在第二个参数中的变量发生改变时,才会执行当前这个副作用钩子。
import ReactDOM from 'react-dom';
import React,{useState,useEffect } from 'react'
function App() {
const [id, setID] = useState(0);
let [ids,changeIDs] = useState(12);
useEffect(()=>{
console.log('触发第一次副作用钩子',id)
})
useEffect(()=>{
console.log('触发第二次钩子')
},[ids])
return (
<div>ID:{id}<button onClick={()=>setID(id+1)}>修改id</button></div>
)
}
ReactDOM.render(<App />,document.getElementById('root'))
可以发现,除了初始化的时候调用了一次第二个副作用钩子,再后续的id发生改变时,ids这个变量并没有发生变化,所以不会触发第二个副作用钩子。
这就是useEffect 第二个参数的作用,且有心的你可能会觉得疑惑,这副作用钩子的第二个参数是一个数组,那意思是不是可以放置多个变量进行控制,事实也是如此:
我们对实例进行简单的修改:
import ReactDOM from 'react-dom';
import React,{useState,useEffect } from 'react'
function App() {
const [id, setID] = useState(0);
let [ids,changeIDs] = useState(12);
let [needChange,changeNeedChange] = useState(0);
useEffect(()=>{
console.log('触发第一次副作用钩子',id)
})
useEffect(()=>{
console.log('触发第二次钩子',needChange)
},[ids,needChange])
setTimeout(()=>{changeNeedChange(20)},1000)
return (
<div>ID:{id}<button onClick={()=>setID(id+1)}>修改id</button></div>
)
}
ReactDOM.render(<App />,document.getElementById('root'))
在组件初始化后的一秒,执行了一次修改needChange变量: