一 Hooks
1.1 React hooks
React hooks是React 16.8中的新增功能,它们使你无需编写类即可使用状态和其他react功能;
下面实现了一个hooks简单的demo
import React, { useState } from 'react';
import Child from './Child';
// use 开头的都是hook
/*
useState 状态
[state, setState] = useState(initState)
state 当前对应的状态
setState修改state的方法
*/
function App() {
const [name, setName] = useState('Stoney');
return (
<div className="App">
<Child
name={name}
setName={setName}
/>
<button>卸载</button>
</div>
);
}
export default App;
child.js
import React, {useState, useEffect} from 'react';
/**
* useEffect 副作用
* useEffect(cb)
* useEffect(cb[,[依赖1, 依赖2]]);
* useEffect相当于:componentDidMount componentDidUpdate componentWillUnmount合体
*/
function Child(props) {
const {name, setName} = props;
const [age, setAge] = useState(18);
useEffect(() => {
console.log("组件更新了");
});
useEffect(() => {
console.log("age变化导致组件更新了");
}, [age]);
useEffect(() => {
console.log("name变化导致组件更新了");
}, [name]);
return (
<div>
<p>name: {name} <br />
<input
type = "text"
value={name}
onChange = {({target}) => {
setName(target.value);
}}
/>
</p>
<p>age: {age} <br />
<input
type = "text"
value={age}
onChange = {({target}) => {
setAge(target.value);
}}
/>
</p>
</div>
)
}
export default Child;
React hooks优势:
- 简化组件逻辑
- 复用状态逻辑
- 无需使用类组件编写
1.2 常用hook
1.2.1 useState
const [name, setName] = useState(initialState);
1.2.2 useEffect
类组件componentDidMount componentDidUpdate componentWillUnmount需要清除的副作用
下面demo通过类组件和函数式组件对比来演示useEffect功能
App.js代码
import React from 'react';
import Effect from './hook/effect';
function App() {
return (
<div>
<Effect />
</div>
);
}
export default App;
effect.js代码
import React, { Component } from 'react';
class Txt extends Component{
componentWillUnmount() {
console.log('组件即将卸载');
}
render() {
let { text, setEdit } = this.props;
return (
<div>{text} <a onClick={() => {
setEdit({
edit: true
})
}}>编辑</a></div>
)
}
}
class Effect extends Component {
state = {
text: '这是今天的课程',
edit: false
}
setEditState = (edit) => {
this.setState({
edit
})
}
componentDidMount() {
console.log("组件挂载完毕");
}
componentDidUpdate() {
console.log("组件更新完毕");
}
render() {
let { text, edit } = this.state;
return (<div>
{
edit ?
<input
type = 'text'
value = {text}
onChange = {
(e) => {
this.setState({
text: e.target.value
})
}
}
onBlur = {
() => {
this.setEditState(false);
}
}
/>
:
<Txt text={text} setEdit={this.setEditState} />
}
</div>);
}
}
export default Effect;
useEffect用法demo
App.js代码
import React, { useState } from 'react';
import Child from './Child';
// use 开头的都是hook
/*
useState 状态
[state, setState] = useState(initState)
state 当前对应的状态
setState修改state的方法
*/
function App() {
const [name, setName] = useState('Stoney');
const [show, setShow] = useState(true);
return (
<div className="App">
{show ? <Child
name={name}
setName={setName}
/> : ""}
<button onClick={() => {
setShow(!show)
}}>{show ? '卸载' : '加载'}</button>
</div>
);
}
export default App;
clild.js
import React, {useState, useEffect} from 'react';
/**
* useEffect 副作用
* useEffect(cb)
* useEffect(cb[,[依赖1, 依赖2]]);
* useEffect相当于:componentDidMount componentDidUpdate componentWillUnmount合体
* 只希望在组件挂载后执行某些事情(componentDidMount)
*/
function Child(props) {
const {name, setName} = props;
const [age, setAge] = useState(18);
useEffect(() => {
console.log("组件挂载完成之后");
return () => {
console.log("组件即将卸载时执行");
}
}, []);
useEffect(() => {
console.log("组件挂载完成之后及更新完成之后");
});
return (
<div>
<p>name: {name} <br />
<input
type = "text"
value={name}
onChange = {({target}) => {
setName(target.value);
}}
/>
</p>
<p>age: {age} <br />
<input
type = "text"
value={age}
onChange = {({target}) => {
setAge(target.value);
}}
/>
</p>
</div>
)
}
export default Child;
加载卸载也可以使用自定义Hooks,代码如新:
import React, { useState } from 'react';
import Child from './Child';
// use 开头的都是hook
/*
useState 状态
[state, setState] = useState(initState)
state 当前对应的状态
setState修改state的方法
*/
function useToggle(init) {
const [off, setOff] = useState(init);
return [off, () => {
setOff(!off);
}];
}
function App() {
const [name, setName] = useState('Stoney');
// const [show, setShow] = useState(true);
const [show, changeShow] = useToggle(true)
return (
<div className="App">
{show ? <Child
name={name}
setName={setName}
/> : ""}
<button onClick={() => {
changeShow(!show)
}}>{show ? '卸载' : '加载'}</button>
</div>
);
}
export default App;
1.2.3 useContext
下面demo实现了跨级组件通信的例子,通过props实现:
import React, { Component, createContext } from 'react';
class Child extends Component {
render() {
return <strong>这是祖先传下来的宝贝:{this.props.info}</strong>
}
}
class Parent extends Component {
render() {
return (
<p>
<Child info={this.props.info} />
</p>
)
}
}
class Context extends Component {
render() {
return <div>
<Parent
info={"今天天气不错"}
/>
</div>
}
}
export default Context;
context
主要API有createContext, Provider, Consumer, contextType;
下面demo实现了Provider和Consumer通信的例子
import React, { Component, createContext } from 'react';
let {Provider, Consumer} = createContext();
class Child extends Component {
render() {
return (
<Consumer>
{(context) => {
return <strong>这是祖先传下来的宝贝:{context.info}</strong>
}}
</Consumer>
)
}
}
class Parent extends Component {
render() {
return (
<p>
<Child />
</p>
)
}
}
class Context extends Component {
render() {
return <div>
<Provider value={{info: '今天天气不错'}}>
<Parent/>
</Provider>
</div>
}
}
export default Context;
contextType用法:
import React, { Component, createContext } from 'react';
import { ConsoleWriter } from 'istanbul-lib-report';
let myContext = createContext();
class Child extends Component {
static contextType = myContext;
render() {
console.log(this.context)
return (
<strong>这是祖先传下来的宝贝:{this.context.info}</strong>
)
}
}
function Child2() {
return (
<myContext.Consumer>
{
context => {
console.log(context);
return <div><strong>这是祖先传下来的宝贝:{context.info}</strong></div>
}
}
</myContext.Consumer>
)
}
class Parent extends Component {
render() {
return (
<div>
<Child />
<Child2 />
</div>
)
}
}
class Context extends Component {
render() {
return <div>
<myContext.Provider value={{info: '今天天气不错'}}>
<Parent/>
</myContext.Provider>
</div>
}
}
export default Context;
useContext用法:
import React, { Component, createContext, useContext } from 'react';
let myContext = createContext();
class Child extends Component {
static contextType = myContext;
render() {
console.log(this.context)
return (
<strong>这是祖先传下来的宝贝:{this.context.info}</strong>
)
}
}
function Child2() {
let {info} = useContext(myContext);
return (
<div><strong>这是祖先传下来的宝贝:{info}</strong></div>
)
}
class Parent extends Component {
render() {
return (
<div>
<Child />
<Child2 />
</div>
)
}
}
class Context extends Component {
render() {
return <div>
<myContext.Provider value={{info: '今天天气不错'}}>
<Parent/>
</myContext.Provider>
</div>
}
}
export default Context;
1.2.4 useReducer
下面demo是useReducer的用法:
import React, { useReducer, createContext, useContext } from 'react';
let myContext = createContext();
function reduce(state = 0, action) {
switch(action.type) {
case 'add':
state += 1;
break;
case 'minus':
state -= 1;
break;
}
return state;
}
function Child() {
let {state, dispatch} = useContext(myContext)
return (
<div>
<button
onClick={() => {
dispatch({
type: 'minus'
});
}}
>-</button>
<span> {state} </span>
<button
onClick={() => {
dispatch({
type: 'add'
});
}}
>+</button>
</div>
)
}
function Reducer() {
let [state, dispatch] = useReducer(reduce, 0);
return (
<myContext.Provider value={{
state,
dispatch
}}>
<Child />
</myContext.Provider>
)
}
export default Reducer;
1.2.5 useCallback
useCallback和useMemo都会在组件第一次渲染的时候执行,之后每次会在其依赖的变量发生改变时再次执行;并且这两个hook都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数;
下面demo是useCallback的用法;
import React, { useState, useCallback } from 'react';
function Callback() {
const [name, setName] = useState("leo");
const [age, setAge] = useState(10);
let age2 = useCallback(() => age < 18 ? "未成年" : "成年人", [age < 18]);
return (<div>
姓名:{name}, <br />
年龄:{age}, <br />
年龄阶段:{age2()}, <br />
<button onClick={() => {
setAge(age + 2);
}}>长大</button>
</div>)
}
export default Callback;
1.2.6 useRef 用法
获取真实dom
import React, {useState, useEffect, useRef} from 'react';
function Child(props) {
const {name, setName} = props;
const [age, setAge] = useState(18);
const div = useRef(null);
useEffect(() => {
console.log(div.current);
});
return (
<div ref={div}>
<p>name: {name} <br />
<input
type = "text"
value={name}
onChange = {({target}) => {
setName(target.value);
}}
/>
</p>
<p>age: {age} <br />
<input
type = "text"
value={age}
onChange = {({target}) => {
setAge(target.value);
}}
/>
</p>
</div>
)
}
export default Child;
打印如下图所示:
记录组件更新之前的值
import React, { useState } from 'react';
import Child from './Child';
function App() {
const [name, setName] = useState('Stoney');
const [show, setShow] = useState(true);
return (
<div className="App">
{show ? <Child
name={name}
setName={setName}
/> : ""}
<button onClick={() => {
setShow(!show)
}}>{show ? '卸载' : '加载'}</button>
</div>
);
}
export default App;
Child.js
import React, {useState, useEffect, useRef} from 'react';
function Child(props) {
const {name, setName} = props;
const [age, setAge] = useState(18);
const div = useRef(null);
const preVal = useRef({
name,
age
});
/**
* useRef(defaultVal);
* 1,获取真实的dom
* 2,记录组件更新之前的值
*/
useEffect(() => {
console.log(div.current);
console.log(preVal.current, name, age);
preVal.current = {
name, age
}
});
return (
<div ref={div}>
<p>name: {name} <br />
<input
type = "text"
value={name}
onChange = {({target}) => {
setName(target.value);
}}
/>
</p>
<p>age: {age} <br />
<input
type = "text"
value={age}
onChange = {({target}) => {
setAge(target.value);
}}
/>
</p>
</div>
)
}
export default Child;
可以添加一个开关,判断组件是挂载还是更新,代码如下所示:
import React, {useState, useEffect, useRef} from 'react';
function Child(props) {
const {name, setName} = props;
const [age, setAge] = useState(18);
const div = useRef(null);
const preVal = useRef({
name,
age
});
/**
* useRef(defaultVal);
* 1,获取真实的dom
* 2,记录组件更新之前的值
*/
useEffect(() => {
if(!preVal.current) {
console.log("更新")
} else {
console.log("挂载");
preVal.current = false;
}
});
return (
<div ref={div}>
<p>name: {name} <br />
<input
type = "text"
value={name}
onChange = {({target}) => {
setName(target.value);
}}
/>
</p>
<p>age: {age} <br />
<input
type = "text"
value={age}
onChange = {({target}) => {
setAge(target.value);
}}
/>
</p>
</div>
)
}
export default Child;
下面demo通过ref来给输入框组件自动获取焦点的功能
import React, { useState, useEffect, useRef } from 'react';
function Txt(props){
let { text, setEdit } = props;
return (
<div>{text} <a onClick={() => {
setEdit(true)
}}>编辑</a></div>
)
}
function Edit(props) {
const {text, setText, setEdit} = props;
let t = useRef(null);
function toScroll() {
let y = window.scrollY;
t.current.style.transform = `translateY(${y}px)`;
}
useEffect(() => {
window.addEventListener("scroll", toScroll);
t.current.select();
return () => {
console.log("组件即将卸载");
window.removeEventListener("scroll", toScroll);
}
}, [])
return (<input
type = 'text'
value = {text}
id="txt"
ref = {t}
onChange = {
(e) => {
setText(e.target.value)
}
}
onBlur = {
() => {
setEdit(false);
}
}
/>)
}
function Effect() {
const [text, setText] = useState('这是今天的课程');
const [edit, setEdit] = useState(false);
// 只监听edit发生改变
useEffect(() => {
console.log("组件更新了");
}, [edit]);
return (<div>
{
edit ?
<Edit
text={text}
setText={setText}
setEdit={setEdit}
/>
:
<Txt text={text} setEdit={setEdit} />
}
{[...(".".repeat(100))].map((item, index) => {
return <div key={index}>页面内容填充</div>
})}
</div>);
}
export default Effect;
下面demo通过ref拿到上一次值的功能
import React, { useState, useEffect, useRef } from 'react';
function Ref() {
const [nub, setNub] = useState(0);
const prev = useRef(nub);
useEffect(() => {
prev.current = nub;
})
return (<div>
<p>当前值: {nub}</p>
<p>上次值: {prev.current}</p>
<button onClick={() => {
setNub(nub + 1);
}}>递增</button>
</div>);
}
export default Ref;
1.2.7 useMemo
demo如下:
import React, { useState } from 'react';
import Child from './Child';
function App() {
const [name, setName] = useState('Stoney');
const [show, setShow] = useState(true);
return (
<div className="App">
{show ? <Child
name={name}
setName={setName}
/> : ""}
<button onClick={() => {
setShow(!show)
}}>{show ? '卸载' : '加载'}</button>
</div>
);
}
export default App;
Child.js代码
import React, {useState, useEffect, useMemo} from 'react';
function Child(props) {
const {name, setName} = props;
const [age, setAge] = useState(18);
const val = useMemo(() => {
console.log("组件即将挂载及更新")
return `姓名: ${name}, 年龄: ${age}`;
}, [name, age]);
// console.log(val)
useEffect(() => {
console.log("组件挂载完成或更新完成")
});
console.log("组件挂载或更新");
return (
<div>
<p>{val}</p>
<p>name: {name} <br />
<input
type = "text"
value={name}
onChange = {({target}) => {
setName(target.value);
}}
/>
</p>
<p>age: {age} <br />
<input
type = "text"
value={age}
onChange = {({target}) => {
setAge(target.value);
}}
/>
</p>
</div>
)
}
export default Child;
下面demo是memo的用法:
import React, { useState, useMemo, useEffect } from 'react';
function Memo() {
const [name, setName] = useState("leo");
const [age, setAge] = useState(10);
let age2 = useMemo(() => {
console.log(1);
return age <= 18 ? '未成年' : '成年人';
}, [age <= 18]);
useEffect(() => {
console.log("更新完成之后")
}, [age]);
console.log("开始更新了");
return (<div>
姓名:{name}, <br />
年龄:{age}, <br />
年龄阶段:{age2}, <br />
<button onClick={() => {
setAge(age + 2);
}}>长大</button>
</div>)
}
export default Memo;
1.3 自定义Hook
通过自定义Hook,可以将组件逻辑提取到可重用函数中;
下面demo展示了自定义hook的用法:
import React, {useState} from 'react';
function useCount(init) {
let [count, setCount] = useState(init);
function add() {
count++;
setCount(count);
}
function minus() {
count--;
setCount(count);
}
return [count, add, minus, setCount];
}
function Hook() {
let [count, add, minus, setCount] = useCount(0)
return (
<div>
<button
onClick={() => {
minus();
}}
>-</button>
<span> {count} </span>
<button
onClick={() => {
add();
}}
>+</button>
<button onClick={() => {
setCount(5);
}}
>自定义设置</button>
</div>
)
}
export default Hook;
1.3 hook使用规则
只在最顶层使用Hook,不要在循环,条件判断或者子函数中调用;
只在React函数中调用hook
React函数组件中
React Hook中