一 React Hooks介绍
1、React Hooks是用来做什么的
对函数型组件进行增强,让函数型组件可以存储状态,可以拥有处理副作用的能力,让开发者在不使用类组件的情况下,实现相同功能
副作用:代码中只要不是把数据转换成视图的代码他就属于副作用,例如发送ajax请求,获取dom,添加点击事件,这都属于副作用代码,在类型的组件中,一般使用生命周期函数去处理副作用,而在函数型组件当中我们就要用hooks去处理这些函数
2、类组件的不足
缺少复用机制:为了复用逻辑增加无实际渲染效果的组件,增加了组件层级显示十分臃肿,增加了调试的难度以及运行效率的降低
类组件经常变得很复杂难以维护:将一组相干的业务逻辑拆分到了多个生命周期函数中;在一个生命周期函数内存在多个不相干的业务逻辑;类成员方法不能保证this指向的正确性
二、React-Hooks的使用
hooks的含义为钩子,React Hooks就是一堆钩子函数,React通过这些钩子函数对函数型组件进行增强,不同的钩子函数提供了不同的功能:
useState() ;useEffects(); useReducer(); useRef(); useCallback(); useContext(); useMemo()
2.1、useState()
用于为函数组件引入状态,之前函数型组件变量在使用完之后会被释放掉,以前不能保存状态变量的,useState()这个方法内部是使用闭包保存状态数据的
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return <div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
}
使用细节:(1)、接收唯一的参数即状态初始值。初始值可以是任意数据类型。
(2)、返回值为数组。数组中存储状态值和更改状态值的方法。方法名称约定以set开头。后面加上状态名称
(3)、方法可以被多次调用,用以保存不同的状态值。
(4)、参数可以是一个函数,函数返回什么,初始值就是什么,函数只会被调用一次,用在初始值是动态值的情况 例子:
const [show, setShow] = useState(() => {
return props.show || 0
})
这段代码是state中获取props传递的属性值,入写在上面,每一次组件渲染的时候都会走获取props属性值的方法这样的代码是没有必要的,而写在给state传递参数这样只会在第一次渲染的时候获取这个默认值
(5)、设置状态方法的参数可以是一个值也可以是一个函数
<button onClick={() => setCount((count) => {return count + 1})}>+</button>
(6)、设置状态值方法的方法本身是异步的
function handleSetCount () {
setCount ((count) => {
const newCount = count + 1
document.title = newCount
return newCount
})
}
用这种方法可以解决数据不同步的问题
2.2、useReducer()
useReducer是另一种让函数组件保存状态的方式,他的使用方式和rudux的reducer是及其相似的,状态都被保存在一个特殊的地方,组件要是想更改状态就要调用dispatch方法去触发一个action,这个action会被reducer函数接收到,在useReducer内部要判断action的类型是什么,再去选择对他进行怎样的处理,然后再以返回值的方式更新这个状态
import React, { useReducer } from 'react'
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return state + 1
}
}
const App = (props) => {
const [count, dispatch] = useReducer(reducer, 0)
return <div>
<span>{count}</span>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</div>
}
useReducer接收两个参数第一个参数就是reducer函数,我们创建reducer函数去进行对数据的处理,第二个参数为状态的初始值;useReducer这个函数的返回值返回一个数组,数组里面有两个值一个是存储的状态,第二个是触发action的dispatch方法。
自己创建的reducer函数接受两个值,一个是存储的状态另外一个就是触发的action
2.3、useContext()
这个钩子函数的作用是在跨组件层级获取数据时简化代码
import React, { createContext, useContext } from 'react';
const contContent = createContext()
function App() {
return <contContent.Provider value = {100}>
<Foo/>
</contContent.Provider>
}
function Foo () {
const value = useContext(contContent)
return <div>{value}</div>
}
export default App;
使用createContext创建一个实例
在外层组件中使用实例的Provider组件包裹底层组件,在组件内部传值
在底层组件中,使用useContext传入生成的createContext实例获取到上层组件传入的值
2.4、useEffect()
让函数型组件拥有处理副作用的能力。类似生命周期函数
1.useEffect执行时机
可以吧useEffect看做componentDidMount、componentDidupdate和componentWillUnmount这三个函数的组合
useEffect(() => {}) => componentDidMount, componentDidupdate
useEffect(() => {}, []) => componentDidMount
useEffect(()=> () => {}) => componentWillUnMount
const App = () => {
const [count, setCount] = useState(0)
// 组件挂在完成之后进行 组件数据更新之后进行
// useEffect(() => {
// console.log('123')
// })
// 组件挂载完成之后进行一次,之后都不再进行
// useEffect(() => {
// console.log('456')
// }, [])
// 组件再被卸载之前进行
useEffect(() => {
return () => {
console.log('组件被卸载了')
}
})
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)} >+1</button>
</div>
)
}
2.useEffect的使用方法
实例:为window对象添加滚动事件
这个事件要在组件挂载之前绑定
const App = () => {
const [count, setCount] = useState(0);
function onScroll () {
console.log('发生滚动了')
}
// 组件挂载完成之后进行一次,之后都不再进行
useEffect(() => {
window.addEventListener('scroll', onScroll)
}, [])
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)} >+1</button>
</div>
)
}
useEffect的第二个参数
他的作用是只有指定数据发生变化时,函数才会执行
const App = () => {
const [count, setCount] = useState(0);
const [person, setPerson] = useState({name: '张三'});
// 组件挂载完成之后进行一次,之后都不再进行
useEffect(() => {
console.log('只有数字滨化')
}, [count])
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)} >+1</button>
<button onClick={() => setPerson({name: 'lisi'})}>setPerson</button>
</div>
)
}
useEffect结合异步函数
useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成返回Promise,如果想进行异步操作,里面就要变成自执行函数,在自执行函数内部使用异步函数
const App = () => {
const [count, setCount] = useState(0);
const [person, setPerson] = useState({name: '张三'});
// 组件挂载完成之后进行一次,之后都不再进行
function getData() {
return new Promise((resolve, reject) =>{
resolve({msg: 'hello'})
})
}
useEffect(() => {
console.log('只有数字滨化')
}, [count])
useEffect(() => {
(async () => {
let response = await getData()
console.log(response)
})()
}, [])
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(count + 1)} >+1</button>
<button onClick={() => setPerson({name: 'lisi'})}>setPerson</button>
</div>
)
}
2.5、useMemo()
1、useMemo的行为类似Vue中的计算属性,可以检测某个值得变化,根据变化值计算新值。
useMemo会缓存计算结果。如果检测值没有发生变化,即使组件重新渲染,也不会重新计算。此行为可以有助于避免在每个渲染上进行昂贵的计算。
2.6、memo方法
性能优化,如果本组件中的数据没有发生变化,阻止组件更新。类似类组件中的PureComponent和shouldComponentUpdate
const App = () => {
const [count, setCount] = useState(0)
const result = useMemo(() => {
return count * 2
}, [count])
return <div>
<span>{result}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<Foo/>
</div>
}
const Foo = memo(
() => {
return (
<div>Foo组件被重新渲染了</div>
)
}
)
export default App
2.7、useCallBack()
性能优化,缓存函数,使组件重新渲染时得到相同的函数实例。
他的使用场景是,当子组件通知父组件的时候,子组件内部状态没有发生改变但是还是进行了重新渲染,memo虽然解决子组件内部状态没有变阻止子组件重新渲染问题,但是当父组件状态改变,组件发生了重新渲染,重新渲染过后每一次生成的改变状态的函数的实例都变了,导致传递给子组件的方法中的实例变了,当传入的函数实例改变,子组件就会认为组件内部的状态发生了改变,他就会导致重新渲染。解决这个问题的方法就是让传给子组件的函数实例都是同一个这里就要用到useCallBack()
const App = () => {
const [count, setCount] = useState(0)
const resetCount = useCallback(() => setCount(0), [setCount])
const result = useMemo(() => {
return count * 2
}, [count])
return <div>
<span>{result}</span>
<button onClick={() => setCount(count + 1)}>+1</button>
<Foo resetCount={resetCount}/>
</div>
}
const Foo = memo(
(props) => {
return (
<>
<div>Foo组件被重新渲染了</div>
<button onClick={props.resetCount}>resetCount</button>
</>
)
}
)
export default App
他的第一个参数是要改变值的回调函数,他的第二个参数是如果setCount这个方法没有变,那么他生成的resetCount的这个实例永远不会变。
2.8、useRef
他的作用是获取dom元素对象
function App() {
const box = useRef()
return <div ref={box}>
<button onClick={() => console.log(box)}>获取div</button>
</div>
}
export default App
他的另外一个作用是保存数据(跨组件周期)
即使组件重新渲染,保存的数据仍然还在。保存的数据被更改不会触发组件重新渲染,他和useState是不一样的useState保存的是状态数据,状态发生改变后会触发组件重新渲染,而useRef存储的数据不是状态数据,即使数据发生变化也不会触发组件进行重新渲染
案例定时器开关:
一开始在元素挂载之后开启定时器,点击按钮关闭定时器,若定时器的实例定义在最外层默认为空比如let timer = null,当数据改变,组件重新渲染,这样timer又变为空了,会导致无法关闭。所以要使用useRef让保存的数据即使组件重新渲染也不让他的值改变
const App = () => {
const [count, setCount] = useState(0)
let timeId = useRef()
useEffect(() => {
timeId.current = setInterval(() => {setCount(count + 1)}, 1000)
}, [])
const stopCount = () => {
clearInterval(timeId.current)
}
return <div>{count}
<button onClick= {stopCount}>清楚定时器</button>
</div>
}
export default App
tip:官方规定用current保存数据
三、自定义Hook
自定义hook是标准的封装和共享逻辑的方式
自定义hook是一个函数,其名称以use开头
自定义hook其实就是逻辑和内置Hook的组合
function useGetPost() {
const [post, setPost] = useState({})
useEffect(()=> {
axios.get('url').then(result => setPost(res.data))
},[])
return [post, setPost]
}
function App() {
const [post, setPost] = useGetPost()
return <div>
<div>{post.title}</div>
<div>{post.body}</div>
</div>
}
案例二:在表单组建的时候,每一个输入框都需要手动绑定状态值和绑定状态值的方法,这个操作就是重复操作
function useUpdateInput(initialValue) {
const [value, setValue] = useState(initialValue)
return {
value,
onChange: event =>setValue(event.target.value)
}
}
function App() {
const userNameInput = useUpdateInput('')
const passwordInput = useUpdateInput('')
const submitForm = event => {
//阻止提交的默认行为
event.preventDefault()
console.log(userNameInput.value)
console.log(passwordInput.value)
}
return (
<form onSubmit={submitForm}>
<input type="text" name="userName" {...userNameInput}/>
<input type="password" name="password" {...passwordInput} />
<input type="submit"/>
</form>
)
}
export default App
四、React路由Hooks
4.1、react-router-dom 路由提供的钩子函数
它包含了四个钩子函数 useHistory, useLocation, useRouteMatch, useParams
这些函数的作用就是获取相关的路由信息
在组建中,想要获取哪个路由的信息,去调用去获取就可以了