React hooks 的入门笔记
写在前面,笔者才疏学浅,写此文章仅做参考!不对之处,敬请海涵!
React v16.8+ 版本增加了 hooks,笔者认为这极大的改变了 React 的开发模式,并且这是有利的。本文主要记录useState
、useEffect
、useReducer
、useContext
等常用的钩子函数。
比较代码
- 不使用 Hooks
class HookTest extends Component {
constructor(props) {
super(props);
this.state = { count:0 }
this.addCount = this.addCount.bind(this)
}
render() {
return ( <div>{this.state.count}<button onClick={this.addCount}>点我加1</button></div> );
}
addCount(){
this.setState({ count: ++this.state.count });
}
}
- 使用 Hooks
function HookTest() {
const [count, setCount] = useState(0);
render() {
return (
<div>
{count}<button onClick={()=>{setCount(count+1)}}>点我加1</button>
</div>
);
}
}
代码简洁程度不言而喻,作为一个初学者,开始就学到精简的写法,还是很不错的!
React 组件化
一切皆为组件、万物都是函数
- 功能(无状态)组件
- Function Component 功能组件也叫做无状态组件,一般只负责渲染
function Hello (){ return( <div> <h1>Function Component</h1> </div> ) }
- 渲染组件
- Presentational Component 和功能(无状态)组件类似 全凭 参数 props
const hello =(props)=>{ return( <div> <h1>Presentational Component</h1> </div> ) }
- 类(有状态)组件
- Class Component 类组件就是状态组件,一般有交互逻辑也业务逻辑
class Hello extend Component{ ...(业务逻辑) return( <div> <h1>Class Component</h1> </div> ) }
写组件的时候,应该考虑是否可以作为无状态组件、是否分离UI组件等。这样有利于后期代码的维护。
Hook 函数必须以 "use" 命名开头,因为这样才方便 eslint 做检查,防止用 condition 判断包裹 useHook 语句。
useState
🐂🍺 可以不用 this
使用
import { useState } from 'react';
fucntion HookTest(){
const [count, setCount] = useState(0);
// 上面是 ES6 解构赋值 定义变量名的同时 设置修改方法 并初始化
// 等同于
// let _useState = useState(0);
// let count = _useState[0];
// let setCount = _useState[1];
// 上面的意思:`useState`这个函数接收的参数是状态的初始值(Initial state),
// 它返回一个数组,这个数组的第0位是当前的状态值,第1位是可以改变状态值的方法函数。
// 所以上面的代码的意思就是声明了一个状态变量为count,并把它的初始值设为0,
// 同时提供了一个可以改变`count`的状态值的方法函数。
// 在 jsx 中使用
return <h1>{count}<button onClick={()=>setCount(count +1)}></button></h1>
}
重点知识
useState
的初始值,只在第一次有效const Child = ({ data }) => { const [name, setName] = useState(data); // 只会在首次渲染组件时执行 return ( <div> <div>child</div> <div> {name} --- {data} // 首次 rose --- rose // 点击按钮后 rose --- jack </div> </div> ); }; const Hook = () => { const [name, setName] = useState('rose'); return ( <div> <button onClick={() => setName('jack')}>update name </button> <Child data={name} /> </div> ); }; export default Hook;
- 不可以在 if 中声明
- React Hooks 并不是通过 Proxy 或者 getters 实现的,而是通过数组实现的,每次
useState
都会改变下标,如果useState
被包裹在 condition 中,那每次执行的下标就可能对不上,导致useState
导出的setter
更新错数据。
- React Hooks 并不是通过 Proxy 或者 getters 实现的,而是通过数组实现的,每次
手写 useState
let state = []; // state数组用来保存数据
let index = 0; // index用来对应每一个数组项
function useState(initialState) {
let currentIndex = index; // currentIndex用来保存当前index
state[currentIndex] = state[currentIndex] || initialState;
function setState(newState) {
state[currentIndex] = newState;
render();
}
index += 1; // 每次修改完成之后index加1
return [state[currentIndex], setState];
}
function render() {
index = 0; // render时需要重新恢复index ReactDom.render(<Counter />, document.getElementById("root"));
}
实现思路如下:
- 将
state
声明成数组,每一个数据对应数组的某一项 - 声明一个索引
index
,每个数据对应一个索引值 setState
通过操作索引去设置值- 每调用一次
useState
需要将 index+=1。这样的话确保多个数据具有不同的索引值 - 返回的值也是通过索引获取
- 每次
render
重新渲染时需要将索引 index 置为 0,确保每个数据对应的索引每次都是一致的(render 渲染组件重新渲染,组件内所有的 useState 会执行一次,每个数据又会分配一个索引,因此每次需要将 index 置为 0,确保每次的索引一致。这也是为什么 hooks 不能写在 if,while 等条件判断中)。
上面最核心的一点就是确保每个 useState 的数据对应的 index 必须一致。 也就是说:
- 第一次渲染时,count 对应的索引值为 0,num 对应的索引值为 1。
- 第二次渲染时,count 对应的索引值仍然为 0,num 对应的索引值为 1。
- ...
useEffect
🐂🍺 在函数组件里面使用 class 组件的生命周期函数,并且还是生命周期函数的集合!
class 组件生命周期
// 挂载阶段(常用到的钩子函数)
componentWillmount()
render()
componentDidMount()
//更新阶段
componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate ()
render()
componentDidUpdate()
// 卸载阶段
componentWillUnmount()
使用 useEffect 替代
// React 首次渲染和之后的每次渲染都会调用一遍 useEffect 函数,
// 而之前我们要用两个生命周期函数
// 分别表示首次渲染( componentDidMount )和更新导致的重新渲染( componentDidUpdate )。
// useEffect 中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数时异步执行的,而
// componentDidMount和 componentDidUpdate 中的代码都是同步执行的。
useEffect(() => {
console.log(123);
});
知识点
- 可以写多个
useEffect
- 第二个参数是一个
[]
,数组中可以写入很多状态对应的变量,意思是当状态值发生变化时,我们才进行解绑。但是当传空数组[]
时,就是当组件将被销毁时才进行解绑,这也就实现了 componentWillUnmount 的生命周期函数- 如果我们想每次 count 发生变化,我们都进行解绑,只需要在第二个参数的数组里加入 count 变量就可以了。
useEffect(() => { console.log('++++'); }, [count]);
- 在
useEffect
的return
里面可以做取消订阅的事- componentWillUnmount 生命周期函数(组件将要被卸载时执行)。比如我们的定时器要清空,避免发生内存泄漏;比如登录状态要取消掉,避免下次进入信息出错。componentWillUnmount -解绑副作用
useEffect(() => { const subscription = 订阅事件! return () => { 解绑事件! } } ,[])
useReducer
🐂🍺 它可以增强我们的Reducer
,实现类似Redux的功能。
了解更多 Redux 的知识可以移步 你想知道的Redux和React-Router都在这里
使用
import React, { useReducer } from 'react';
function ReducerDemo(){
const [ count , dispatch ] =useReducer((state,action)=>{
switch(action){
case 'add':
return state+1
case 'sub':
return state-1
default:
return state
}
},0)
return (
<div>
<h1>{count}</h1>
<button onClick={()=>dispatch('add')}>Increment</button>
<button onClick={()=>dispatch('sub')}>Decrement</button>
</div>
)
}
export default ReducerDemo
useContext
🐂🍺 useContext 跨越组件层级直接传递变量,实现共享。
需要注意的是 useContext 和 redux 的作用是不同的,一个解决的是组件之间值传递的问题,一个是应用中统一管理状态的问题,但通过和 useReducer 的配合使用,可以实现类似 Redux 的作用。
import React, {
useState,
useEffect,
createContext,
useContext,
useReducer
} from 'react';
// 1、创建一个 createContext
const CountContext = createContext();
function Counter() {
const count = useContext(CountContext); //一句话就可以得到count
return <h2>{count}</h2>;
}
function HookTest() {
const [count, dispatch] = useReducer((state, action) => {
switch (action) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}, 0);
return (
<div>
{/* 2、 创建一个上下文变量 */}
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
{/* {count} */}
<button
onClick={() => {
dispatch('add');
}}
>
加1
</button>{' '}
<button
onClick={() => {
dispatch('sub');
}}
>
减1
</button>
</div>
);
}
export default HookTest;
使用useContext
和useReducer
是可以实现类似Redux
的效果
使用步骤
- 创建一个 createContext;const CountContext = createContext();
- 创建一个上下文变量
<CountContext.Provider value={count}> <Counter /> </CountContext.Provider>
- 在子组件中通过 useContext获得
function Counter() { const count = useContext(CountContext); //一句话就可以得到count return <h2>{count}</h2>; }
总结
路漫漫其修远兮,吾将上下而求索。笔者目前所遇之困境,唯学习、自省、锻炼可解。另外,笔者希望屏幕面前的你能不断学习、每日自省、加强锻炼......