函数是组件
- 函数式组件,本质就是一个常规函数,接收一个参数 props 并返回一个 reactElement
- 函数式组件中没有this和生命周期函数/state,不能使用 string ref
- 使用函数式组件时,应该尽量减少在函数中声明子函数,否则,组件每次更新时都会重新创建这个函数
hooks 常用的钩子
说在前面,hooks方法只能放在函数的最顶层,不能利用其它函数将它包起来。
useState — 赋予函数组件 状态
let [状态,修改该状态的方法] = useState(初始值);
1. 在同一个组件中可以使用 useState 定义多个状态
2. 注意 useState 返回的 setState 方法,不会进行对象合并
3. 注意 useState 返回的 setState 方法同样是异步方法
// 一个简单的例子
function App(props) {
const [data,setData] = useState({
// 一般不会在初始化的时候传对象,只是为了涉及例子
count: 1,
name: "kkb"
});
console.log("render");
let {count,name} = data;
return <div>
<p>{name}</p>
<p>{count}</p>
<button onClick={()=>{
setData({
...data, // 因为不进行对象合并,所以先拷贝一份
count: count + 1
});
console.log(data.count); // setState是一个异步方法,你会发现在这里打印出来,其实count还没有变
}}>递增</button>
</div>
注意:useState 下的 setState 方法,也会进行浅对比 —> 也就说,在state没有改变的时候,render方法是不会进行调用的 == 组件不会进行渲染
在父子组件通讯中也要注意:
// child.js
function Child(props) {
let {data,setData} = props;
let {name,count} = data;
return <div>
<p>{name}</p>
<p>{count}</p>
<button onClick={()=>{
data.count++;
setData(data); // 这里的data是引用类型,setState在进行浅对比的时候,引用地址并没有变,所以不会进行视图更新
// 正确的做法
setData({...data})
}}>递增</button>
</div>
}
useEffect — 挂载完成或者更新完成
副作用函数
// 1.组件挂载完成或更新完成之后执行
useEffect(()=>{});
// 2.组件挂载完成 或data修改引起的组件更新完之后执行
useEffect(()=>{},[data]);
//3.组件挂载完成
useEffect(()=>{},[]);
//4.//返还函数
useEffect(()=>{
return ()=>{console.log('返还函数!')}
});
挂载过程:
组件挂载成功 --> 回调函数
更新过程:(如果有地方只想在更新的时候执行,那么我们就要给他一个变量进行判断)
组件即将更新 --> 执行 useEffect 的返还函数 --> 组件更新 --> useEffect 的回调函数
卸载过程:
执行 useEffect 的返还函数
tips:其实卸载的过程也可以看做更新的过程,只是在shouldComponentUpdate的时候,return的值为false,那么就会不进行下面的生命周期了(有待考证)
useRef — 获取DOM节点 、
- 获取DOM节点的时候,用法与createRef一样
- 当 useRef 中存储的是组件中的状态(或其他非关联DOM)时,组件更新 ref 中保存值并不会自动更新,需要我们手动更新<通过useRef来获取组件更新前数据>
注意:_useRef是组件内部的,不同于全局变量(多个组件共用),所以可以用来做一些跨状态的标记。function Child(props) {
const {name,setName} = props;
const [count,setCount] = useState(1);
const prevCount = useRef(count);
const countP = createRef(); // 与createRef作对比
useEffect(()=>{
console.log(countP);
console.log(count,prevCount);
prevCount.current = count; // 在组件即将更新的时候,获取count的值,这样就可以报存,上一次更新的数据
},[count]);
return <div>
<p ref={countP}>{name}</p>
<button onClick={()=>{
setName("pika");
}}>中文名</button>
<p>{count}</p>
<button onClick={()=>{
setCount(count+1);
}}>递增</button>
</div>
}
自定义hooks
自定义 Hook 是一个函数,其名称以 “
use
” 开头,函数内部可以调用其他的 Hook。
当我们两个函数之间的代码有共同的逻辑时,可以把这一段共享逻辑提取到一个自定义的hooks里,通过调用这个hooks减少大量的重复性代码。现在贴一个官网上面的实例:
// 官网例子
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
这时候我们就可以在需要 FriendStatus 组件的地方为所欲为、为所欲为:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
值得注意的是,在两个相同的组件中使用Hooks是不会共享state的,它是一种重用状态逻辑的机制,所以每次使用自定义Hooks的时候,state和副作用是完全隔离的。
一个小技巧
在JSX中插入style,一般不能用
function App() {
return <Fragment>
<style>
{`
#root div {
width: 200px;
height: 200px;
border: 2px solid red;
}
`}
</style>
<div>1</div>
<div>2</div>
<div>3</div>
</Fragment>
}