Hook 的含义
1.Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。2.Hook 使你在无需修改组件结构的情况下复用状态逻辑。 这使得在组件间或社区内共享 Hook 变得更便捷3.React 默认提供的四个最常用的钩子。 useState() useContext() useReducer() useEffect()
useState()状态钩子
1.useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。2.这是一种在函数调用时保存变量的方式 —— useState 是一种新方法,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留。
声明 State
import React, { Suspense, useState, useEffect } from "react";
//sueState是接受状态的初始值的
//他默认有两个成员变量第一个是状态的初始值----第二个是修改初始值状态的函数,这个函数必须以set开头的
const [buttonText, setButtonText] = useState("1");
读取 State
当我们想在 class 中显示当前的 count,我们读取 this.state.buttonText:
<p>You clicked {this.state.buttonText} times</p>
在函数中,我们可以直接用 count:
<p>You clicked {buttonText} times</p>
更新 State
在 class 中,我们需要调用 this.setButtonText() 来更新 buttonText 值:
<button onClick={() => this.setButtonText({ count: this.state.buttonText + 1 })}>Click me</button>
在函数中,我们已经有了 setCount 和 count 变量,所以我们不需要 this:
<button onClick={() => setButtonText(buttonText + 1)}>Click me</button>
useContext():共享状态钩子
- 如果需要在组件之间共享状态,可以使用useContext()。
如果使用数据共享的话必须在声明一个公共的AppContext---AppContext = React.createContext({})
必须使用AppContext.Provider标签包裹住,
value={{username:buttonText}} value声明的值就是传递的值
<AppContext.Provider value={{username:buttonText}}>
<div className="App">
<Navbar />
<Messages />
</div>
</AppContext.Provider>;
设置共享状态
import React from "react";
export const AppContext = React.createContext({})
Navbar代码
import React, { useState,useContext } from "react";
import {AppContext} from "./AppContext";
export default function Navbar(){
const [Navbar,steNavbar] = useState("我是Navbar")
const { username } = useContext(AppContext);
function handleClick(){
return steNavbar('我是Navbar222222')
}
return <dev className="Navbar">
<button onClick={handleClick}>{Navbar}我是传递下来的----{username}</button>
</dev>
}
Messages代码
import React, { useState ,useContext} from "react";
import {AppContext} from "./AppContext";
export default function Messages(){
const { username } = useContext(AppContext);
const [Messages,steMessages] = useState("我是Messages")
function handleClick(){
return steMessages('我是Messages222222')
}
return <dev className="Messages">
<button onClick={handleClick}>{Messages}我是传递下来的----{username}</button>
</dev>
}
useReducer():action 钩子
useReducer是针对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer第一个参数:reducer函数第二个参数:初始化的state。按照官方的说法:对于复杂的state操作逻辑,嵌套的state的对象,推荐使用useReducer。
声明useReducer
使用useState版login实现--如果使用他实现在login函数中当登录成功、失败时进行了一系列复杂的state设置。可以想象随着需求越来越复杂更多的state加入到页面,更多的setState分散在各处,很容易设置错误或者遗漏,维护这样的老代码更是一个噩梦
function LoginPage() {
const [name, setName] = useState(''); // 用户名
const [pwd, setPwd] = useState(''); // 密码
const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
const [error, setError] = useState(''); // 错误信息
const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录
const login = (event) => {
event.preventDefault();
setError('');
setIsLoading(true);
login({ name, pwd })
.then(() => {
setIsLoggedIn(true);
setIsLoading(false);
})
.catch((error) => {
// 登录失败: 显示错误信息、清空输入框用户名、密码、清除loading标识
setError(error.message);
setName('');
setPwd('');
setIsLoading(false);
});
}
return (
// 返回页面JSX Element
)
}
useReducer版login
const initState = {
name: '',
pwd: '',
isLoading: false,
error: '',
isLoggedIn: false,
}
function loginReducer(state, action) {
switch(action.type) {
case 'login':
return {
...state,
isLoading: true,
error: '',
}
case 'success':
return {
...state,
isLoggedIn: true,
isLoading: false,
}
case 'error':
return {
...state,
error: action.payload.error,
name: '',
pwd: '',
isLoading: false,
}
default:
return state;
}
}
function LoginPage() {
const [state, dispatch] = useReducer(loginReducer, initState);
const { name, pwd, isLoading, error, isLoggedIn } = state;
const login = (event) => {
event.preventDefault();
dispatch({ type: 'login' });
login({ name, pwd })
.then(() => {
dispatch({ type: 'success' });
})
.catch((error) => {
dispatch({
type: 'error'
payload: { error: error.message }
});
});
}
return (
// 返回页面JSX Element
)
}
可以看到login函数现在更清晰的表达了用户的意图,开始登录login、登录success、登录error。LoginPage不需要关心如何处理这几种行为,那是loginReducer需要关心的,表现和业务分离。
useEffect():副作用钩子
useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()。
useEffect(() => {
// Async Action
// alert(22222)
}, [buttonText])
注意(useEffect需要清除的的数据)
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate。传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount。传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行我们研究了如何使用不需要清除的副作用,还有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
function App () {
const [ count, setCount ] = useState(0)
const [ width, setWidth ] = useState(document.body.clientWidth)
const onChange = () => {
setWidth(document.body.clientWidth)
}
useEffect(() => {
// 相当于 componentDidMount
console.log('add resize event')
window.addEventListener('resize', onChange, false)
return () => {
// 相当于 componentWillUnmount
window.removeEventListener('resize', onChange, false)
}
}, [])
useEffect(() => {
// 相当于 componentDidUpdate
document.title = count
})
useEffect(() => {
//每次点击都会调用
console.log(`count change: count is ${count}`)
}, [ count ])
return (
<div>
名称: { count }
宽度: { width }
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
第一个 useEffect 中的 ‘add resize event’ 只会在第一次运行时输出一次,无论组件怎么 render,都不会在输出,第二个 useEffect 会在每次组件 render 之后都执行,title 每次点击都会改变, 第三个 useEffect, 只要有在第一次运行和 count 改变时,才会执行,屏幕发生改变引起的 render 并不会影响第三个 useEffect。
useRef()
- useRef 返回一个可变的 ref 对象
- 可以保存任何类型的值:dom、对象等任何可变化的值
- ref对象与自己一个对象的区别是:useRef会在每次渲染时返回同一个ref对象,即返回的ref对象在组件的整个生命周期内保持不变。自己创建对象每次渲染时都建立一个新的。
- ref对象的值发生改变之后,不会触发组件重新渲染。 例子:
import React, {
useRef,
useEffect,
} from 'react'
import Child from'./Child'
const Dome = () => {
const domRef = useRef<any>(null);
const childRef = useRef<any>(null)
useEffect(() => {
console.log("ref:deom-init", domRef, domRef.current);
console.log("ref:child-init", childRef, childRef.current);
});
const showChild = () => {
console.log("ref:child", childRef, childRef.current);
childRef.current.say()
};
return (
<div style={{ margin: "100px", border: "2px dashed", padding: "20px",background:'red' }}>
<h2>这是外层组件</h2>
<div
onClick={() => {
console.log("ref:deom", domRef, domRef.current);
domRef.current.value = 'hh';
}}
>
<label>我是一个输入框</label><input ref={domRef} />
</div>
<br />
<p onClick={showChild} style={{ marginTop: "20px" }}>
这是子组件
</p>
<div style={{ border: "1px solid", padding: "10px" }}>
<Child ref={childRef} />
</div>
</div>
)
}
export default Dome
子组件
import React, {
useImperativeHandle,
forwardRef,
} from 'react'
const ChildComponent = (props, ref) => {
useImperativeHandle(ref, () => ({
say: sayHello,
name:'22222'
}));
const sayHello = () => {
alert("hello,我是子组件");
};
return <h3>子组件</h3>;
};
export default forwardRef(ChildComponent)
useImperativeHandle(ref,createHandle,[deps])可以自定义暴露给父组件的实例值。如果不使用,父组件的ref(chidlRef)访问不到任何值(childRef.current==null)useImperativeHandle应该与forwradRef搭配使用React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。React.forward接受渲染函数作为参数,React将使用prop和ref作为参数来调用此函数。
参考链接
react.docschina.org/docs/hooks-… www.ruanyifeng.com/blog/2019/0… blog.csdn.net/adley_app/a… blog.csdn.net/weixin_4372…