React组件分为类式组件与函数式组件,也成为状态组件与无状态组件,当你的组件需要状态对象时,大多数的写法都是Class,它有this、有生命周期、而function既没有自己的this,也没有生命周期。如何使用function来编写有状态、有生命周期的组件呢,那么就要介绍React
16.8版本后退出的新特性——Hooks
React Hooks
- useState(状态)
- useRef(Ref)
- useEffect(副作用)
- useContext(上下文)
- useReducer(Redux)
- 自定义Hook
useState
useState钩子可以创建一个状态对象以及设置状态对象的方法,用法如下:
const [count, setCount] = React.useState(0)
其中useState(initState, setState)
接收两个参数,第一个参数initState
为初始化状态数据,第二个参数setState
为设置状态的方法,一般第一个参数需要设置,如果不设置count则为undefined。
例子:
import { useState } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const add = () => {
setCount(count + 1);
};
return (
<div className="App">
<h1>我是父组件</h1>
<p>Count: {count}</p>
<button onClick={add}>加1</button>
</div>
);
}
这里由useState创建出的count类似于Class组件中state中创建的对象,而setCount与Class组件中的setState相似(都可以传递一个值或者一个函数)
useRef
useRef用于创建一个ref对象,可以在必要的时候在ref对象中current
中读取该ref的值,用法如下:
const InputRef = useRef()
同样useRef(initRef)
也可以传递一个参数,相当于给ref的current
创建一个默认值
例子:
import { useState, useRef } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const InputRef = useRef();
const add = () => {
setCount(count + InputRef.current.value * 1);
};
return (
<div className="App">
<h1>我是父组件</h1>
<p>Count: {count}</p>
<input ref={InputRef} />
<button onClick={add}>求和</button>
</div>
);
}
useEffect
开篇说了function组件没有自己的状态与生命周期,通过useState
可以创建状态对象,而使用useEffect
可以模拟生命周期,例如Class组件中的 componentDidMount
、componentDidUpdate
和componentWillUnMount
生命周期。
useEffect接受两个参数,可以通过传递不同的参数来模拟上述的生命周期
模拟componentDidMount
例子:
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const add = () => {
setCount(count + 1);
};
useEffect(() => {
console.log("useEffect,count:",count);
},[]);
return (
<div className="App">
{console.log("render, count:",count)}
<h1>我是父组件</h1>
<p>Count: {count}</p>
<button onClick={add}>求和</button>
</div>
);
}
当组件第一次渲染后,可以在控制台看到,useEffect
在组件挂载后执行,此时和Class组件中的componentDidMount
作用是一样的。
当你再次点击后,你会发现,只有组件被更新了,而useEffect
不输出了
模拟componentDidUpdate
例子:
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [count, setCount] = useState(0);
const [Name, setName] = useState("法外狂徒");
const add = () => {
setCount(count + 1);
};
const changeName = () => {
setName((preName) => preName === "法外狂徒" ? "张三" : "法外狂徒")
}
useEffect(() => {
console.log(`useEffect,count:${count}, Name:${Name}`);
}, [count]);
return (
<div className="App">
{console.log(`render,count:${count}, Name:${Name}`)}
<h1>我是父组件</h1>
<p>Name: {Name}</p>
<p>Count: {count}</p>
<button onClick={add}>求和</button>
<button onClick={changeName}>换名</button>
</div>
);
}
此时,点组件挂载后,分别点击求和按钮与换名按钮后会发现,点击求和后useEffect会输出,状态也发生了改变,而点击换名按钮后,虽然状态发生了改变,但是useEffect却没有输出。因为在useEffect中传递的第二个参数中,只对count
进行了更新的监听(有点Vue中的Watch内味了吗),当count
状态更新后,会执行传递的方法,而其他状态更新就不会执行。
模拟componentWillUnMount
例子:
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [flag, setFlag] = useState(true);
const switchComponent = () => {
setFlag(!flag);
};
useEffect(() => {
console.log(`useEffect App`);
return () => {
console.log("Component unMount");
};
}, []);
return (
<div className="App">
{console.log(`render`)}
<h1>我是父组件</h1>
<button onClick={switchComponent}>切换</button>
{flag ? <A /> : <B />}
</div>
);
}
function A() {
useEffect(() => {
console.log(`useEffect >>> A`);
return () => {
console.log("A Component unMount");
};
}, []);
return <div>我是A组件</div>;
}
function B() {
useEffect(() => {
console.log(`useEffect >>> B`);
return () => {
console.log("B Component unMount");
};
}, []);
return <div>我是B组件</div>;
}
上述例子中,模拟了一个组件加载与卸载的效果,点击切换按钮可以将A组件与B组件来回挂载到页面中。
当第一次页面渲染时,父组件和子组件的useEffect
都会被执行,和Classs组件的生命周期相同。
第一次点击切换按钮,A组件卸载,B组件挂载
这时候可以发现,A组件卸载的时候,执行了useEffect
返回的方法,而这个方法正是模拟了Class组件中的componentWillUnMount
生命周期。
第二次点击切换按钮,B组件卸载,A组件挂载
可以通过这个方法来在function组件模拟componentWillUnMount
useContext
在Class组件中,组件实例对象中有一个context
属性用于隔层传递数据,在function组件中,可以使用useContext
来进行创建context
例子:
import { useState, useContext, createContext } from "react";
import "./styles.css";
const AppContext = createContext({});
export default function App() {
const [ name, setName ] = useState("张三");
return (
<div>
<h1>我是父组件</h1>
<AppContext.Provider value={{ name: name }}>
<Child />
</AppContext.Provider>
</div>
);
}
function Child() {
return (
<div>
<h2>我是子组件</h2>
<Grand />
</div>
);
}
function Grand() {
const { name } = useContext(AppContext);
return (
<div>
<h3>我是孙组件: App组件name为:{name}</h3>
</div>
);
}
在父组件中通过createContext
来创建出一个上下文,并在挂载子组件的位置提供Provider
来给后代组件中提供数据,在需要使用数据的子孙组件中,使用useContext
来使用父组件传递过来的数据。
useReducer
顾名思义,useReducer
可以让function组件使用redux,用法:
const [state, dispatch] = useReducer(reducer, initState)
useReducer
接收三个参数,第一个参数为改变状态的reducer方法,第二个参数为初始化的state数据,第三个参数可以接收一个惰性初始化函数,用于计算返回一个初始化state数据。
例子:
import { useReducer, useRef } from "react";
import "./styles.css";
const initState = 0;
const reducer = (state = initState, action) => {
switch (action.type) {
case "add":
return state + action.value;
case "minus":
return state - action.value;
default:
return state;
}
};
export default function App() {
const InputRef = useRef();
const [state, dispatch] = useReducer(reducer, initState);
const add = () => {
dispatch({ type: "add", value: InputRef.current.value * 1 });
};
const minus = () => {
dispatch({ type: "minus", value: InputRef.current.value * 1 });
};
return (
<div className="App">
<h1>我是父组件</h1>
<p>Count: {state}</p>
<input ref={InputRef} />
<button onClick={add}>加</button>
<button onClick={minus}>减</button>
</div>
);
}
自定义Hook
我们还可以通过自定义一些Hook来满足我们开发的需要,例如,封装一个获取列表数据的Hook,
伪代码:
import { useState, useEffect } from "react";
import "./styles.css";
const fetchInitData = () => {
const [dataList, setDataList] = useState([])
useEffect(async () => {
await ajax.get('/getDataList').then(res => {
setDataList(dataList)
})
},[])
return {
dataList,
setDataList
}
}
export default function App() {
useEffect(() => {
fetchInitData()
},[])
return (
<div className="App">
<h1>我是父组件</h1>
</div>
);
}
码字不易,给个赞再走吧~