首先 useState 是一个Hook,它容许您将React状态添加到功能组件javascript
useState 是一个方法,它自己是没法存储状态的css
其次,他运行在 FunctionalComponent 里面,自己也是没法保存状态的html
useState 只接收一个参数 inital value,并看不出有什么特殊的地方。java
由于类组件有不少的痛点react
bind
,this
指向不明确 好比 常常看到这样的写法。// 多是这样
class MyComponent extends React.Component {
constructor() {
// initiallize
this.handler1 = this.handler1.bind(this)
this.handler2 = this.handler2.bind(this)
this.handler3 = this.handler3.bind(this)
this.handler4 = this.handler4.bind(this)
this.handler5 = this.handler5.bind(this)
// ...more
}
}
// 多是这样的
export default withStyle(style)(connect(/*something*/)(withRouter(MyComponent)))
复制代码
开始以前先看一个简单的例子,在没有 Hooks 以前咱们是这样来写的。ios
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
Switch: "打开"
};
}
setSwitch = () => {
this.state.Switch === "打开"
? this.setState({ Switch: "关闭" })
: this.setState({ Switch: "打开" });
};
render() {
return (
<div> <p>如今是: {this.state.Switch}状态</p> <button onClick={this.setSwitch}>Change Me!</button> </div>
);
}
}
export default CommonCollectionPage;
复制代码
之前函数式组件须要给他本身的状态的时候咱们老是不得不把函数式组件变成 Class 类组件,如今有了 React Hooks 咱们在也不须要由于一个小状态而将函数式组件变成类组件,上面的这个例子,就能够变成下面的这个方法来表现。git
function App() {
const [Switch, setSwitch] = useState("打开");
const newName = () =>
Switch === "打开" ? setSwitch("关闭") : setSwitch("打开");
return (
<div> <p>如今是: {Switch} 状态</p> <button onClick={newName}>Change Me!</button> </div>
);
}
复制代码
因此 useState 就是为了给函数式组件添加一个能够维护自身状态的功能。github
动态传递参数给 useState 的时候只有第一次才会生效闭包
经过 useEffect 方法来代替了 class 类组件的 componentDidMount
, componentDidUpdate
, componentWillUnmount
三个组件,那如何一个钩子怎么使用才有 3 个钩子的不一样效果呢:
首选咱们先运行起来:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
const [switch, setSwitch] = useState("打开");
const handleSwitch = _ =>
switch === "打开" ? setSwitch("关闭") : setSwitch("打开");
const [num, setNum] = useState(0);
const add = () => {
setNum(num + 1);
};
const minus = () => {
setNum(num - 1);
};
useEffect(() => {
console.log("改变了状态");
}, [num]);
return (
<div> <p>如今是: {switch}状态</p> <button onClick={handleSwitch}>Change Me!</button> <p>数字: {num}</p> <button onClick={add}> +1 </button> <button onClick={minus}> -1 </button> </div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement); 复制代码
上面这个例子他会再初次渲染的时候打印 改变了状态 而且每次状态改变的时候都打印 改变了状态
那么这样的用法就是 componentDidMount
, componentDidUpdate
,的使用
useEffect 方法中加入第二个参数 一个空对象 [ ] 那么以后只会在首次组件装载好的时候打印一次
改变状态了
如今咱们须要当 num 改变状态的时候 去打印 怎么办呢?
刚刚咱们使用了一个空对象 那么只须要再这个对象中加入咱们须要监听的状态那么他至关于使用了 , componentDidUpdate
, 钩子函数例如
useEffect(() => {
console.log("改变了状态");
}, [num]);
复制代码
那么如今,当 activeUser 状态改变的时候咱们会发现又打印出了 改变状态 这句话。而当 switch 状态改变的时候并不会打印这句话。
import React, { useState, useEffect } from 'react';
function App() {
useEffect(() => {
console.log('装载了')
return () => {
console.log('卸载拉');
};
});
return (
<div> xxxx </div>
);
}
export default App;
复制代码
在 useEffect 中咱们能够作两件事情,组件挂载完成时候,还有组件卸载时,只要在 useEffect 中使用闭包,在闭包中作咱们想要在组件卸载时须要作的事就能够。
首先咱们回顾下之前咱们经常用的类组件,下面是一段实现计数器的代码:
import React, { Component, useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends Component {
constructor() {
super();
this.state = {
count: 0
};
}
componentDidMount() {
setTimeout(() => {
console.log(`count:${this.state.count}`);
}, 3000);
}
componentDidUpdate() {
setTimeout(() => {
console.log(`count:${this.state.count}`);
}, 3000);
}
render() {
return (
<div> <p>{this.state.count}</p> <button onClick={() => this.setState({ count: this.state.count + 1 }) } > 点击 3 次 </button> </div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement); 复制代码
页面刷新当即,点击 3 次按钮,上述这个例子的打印结果会是什么????
咱们很清楚的了解 this.state 和 this.setState 全部咱们会知道打印的是:
一段时间后依此打印 3,3,3,3
不过 hooks 中 useEffect 的运行机制并非这样运做的。
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setTimeout(() => {
console.log(count);
}, 3000);
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> 点击 3 次 </button> </div>
);
}
复制代码
一段时间后依此打印 0,1,2,3。
其实没有之前 this.state 使用印象,看到这段代码的打印结果,会认为这不是理所固然的吗?
那咱们想让上面的类组件,也实现上述 0,1,2,3,4 效果 咱们增长这 2 行代码
//...
componentDidMount() {
// 新增此行代码
const newCount = this.state.count;
setTimeout(() => {
console.log(newCount);
}, 3000);
}
componentDidUpdate() {
// 新增此行代码
const newCount = this.state.count;
setTimeout(() => {
console.log(newCount);
}, 3000);
}
// ....
复制代码
全部咱们会联想到 在函数式组件中 取值下面的效果是同样的
function Counter(props) {
useEffect(() => {
setTimeout(() => {
console.log(props.counter);
}, 3000);
});
// ...
}
复制代码
function Counter(props) {
const counter = props.counter;
useEffect(() => {
setTimeout(() => {
console.log(counter);
}, 3000);
});
// ...
}
复制代码
这一点说明了在渲染函数式组件的时候他的更新不会改变渲染范围内的 props ,state 的值。(表达可能有误)
固然,有时候咱们会想在effect的回调函数里读取最新的值而不是以前的值。就像以前的类组件那样打印 3.3.3.3。这里最简单的实现方法是使用useRef。
首先实现上诉打印 3,3,3,3 的问题。以下代码所示
function Counter() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
latestCount.current = count;
setTimeout(() => {
console.log(latestCount.current);
}, 3000);
});
return (
<div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> 点击 3 次 </button> </div>
);
}
复制代码
咱们经过 useRef(initVal) 来返回一个可变的 ref 对象,其 current 属性被初始化为传递的参数 (initVal)。
而后函数式组件没有生命周期,那咱们怎么才能获取 ChartDom 真实的 dom 元素呢?也能够经过 useRef 实现
import React, { useState, useRef, Fragment, useEffect } from 'react';
import { Button } from 'antd';
function Demo({ count: propsCount = 1 }) {
const [count, setCount] = useState(propsCount);
const refContainer = useRef(null); // 如同以前的 React.createRef();
useEffect(() => {
console.log(refContainer.current, '>>>>>>>>>>>');
});
return (
<Fragment> <Button onClick={() => { setCount(count + 1); }}>Click Me</Button> <p ref={refContainer}>You click {count} times</p> </Fragment>
);
}
export default Demo;
复制代码
import React, { useReducer, useEffect } from "react";
import ReactDOM from "react-dom";
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
useEffect(() => {
const id = setInterval(() => {
dispatch({ type: 'tick' });
}, 1000);
return () => clearInterval(id);
}, [dispatch]);
return (
<>
<h1>{count}</h1>
<input value={step} onChange={e => {
dispatch({
type: 'step',
step: Number(e.target.value)
});
}} />
</>
);
}
const initialState = {
count: 0,
step: 1,
};
function reducer(state, action) {
const { count, step } = state;
if (action.type === 'tick') {
return { count: count + step, step };
} else if (action.type === 'step') {
return { count, step: action.step };
} else {
throw new Error();
}
}
复制代码
在类组件中咱们都是经过使用 ref 的方式 获取类组件的实例,这样就可让父组件调用子组件的方法。
那么函数式没有实例,怎么使用 ref 呢?
// 子组件
import React, { useRef, useImperativeHandle,forwardRef } from "react";
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />; } export default forwardRef(FancyInput); 复制代码
// 父组件
import React, { useRef } from "react";
function App(){
const fancyInputRef = useRef(null)
// 这样获取子组件方法
fancyInputRef.current.focus()
return (
<div> <FancyInput ref={fancyInputRef} /> </div> ) } 复制代码
全文章,若有错误或不严谨的地方,请务必给予指正,谢谢!
我的其余文章推荐:
参考: