react hooks源码 感觉有点复杂。现在只能学习用法和写个简易版的hooks。 在此之前,想先了解下几个出现的名词
状态组件 VS 非状态组件
推荐阅读有状态和无状态组件之间的区别
状态组件
无状态组件
没有自己的state和生命周期函数。接受一个props,只是纯props展示组件,不涉及状态的更新
export const Button=(props)=>{
return(
<Button className={this.props.className)/>
)
}
状态组件
有自己的state和生命周期函数。比如下面的内部的状态会受到外部传的props改变而改变
export class Header extends React.Component {
constructor(props) {
super(props);
this.state = {
title: ''
}
};
render() {
return (
<Header title={this.state.title}/>
)
}
}
什么情况用状态组件和无状态组件
当不涉及state的更新时,用无状态组件,效率高,复用性高;反之亦然。
受控组件 VS 非受控组件
推荐阅读controlled-vs-uncontrolled-inputs-react
受控组件
非受控组件
表单数据由DOM节点来处理,换句话来说就是使用ref来获取值,如
class Form extends React.Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.input = React.createRef();
}
handleInputChange() {
alert( this.input.current.value);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} onChange={this.handleInputChange}/>
</label>
</form>
);
}
}
受控组件
受控组件接受当前的value值作为prop,并且可以通过回调改变value,如
class Form extends React.Component {
constructor() {
super();
this.state = {
name: '',
};
}
handleInputChange = (event) => {
this.setState({ name: event.target.value });
};
render() {
return (
<div>
<input
type="text"
value={this.state.name}
onChange={this.handleInputChange}
/>
</div>
);
}
}
受控组件和非受控组件什么时候用
| 特征 | 非受控组件 | 受控组件 |
|---|---|---|
| 一次性获取(如submit) | √ | √ |
| 提交时验证 | √ | √ |
| 实时验证 | x | √ |
| 有条件地禁用submit按钮 | x | √ |
| 强制输入格式 | x | √ |
| 一个数据的多次输入 | x | √ |
| 动态的输入 | x | √ |
useState
用法
const [number, setNumber] = useState(0);
return (
<div>
<div>{number}</div>
<button onClick={() => setNumber(number)}>点我</button>
</div>
);
看起来比较简单。返回一个state和更新state的函数。每次state改变的时候,加入渲染更新队列,即视图发生改变。每一次更新state的函数返回最后更新的值。哪么问题来了,如何加入更新队列?如何返回最后更新的值,其实实现方式很简单
简易版的useState
let index = 0;
let hookState = [];
function useState(initalState){
let currentIndex = index;
hookState[currentIndex] = initalState;
function setState(newState){
hookState[currentIndex] = newState // 加入到更新队列
render() // 渲染
}
// 返回最后最后一次更新的值
return [ hookState[index++],setState]
}
function Counter() {
const [number, setNumber] = useStates(0);
return (
<div>
<div>{number}</div>
<button onClick={() => setNumber(number + 1)}>点我</button>
</div>
);
}
function render() {
index = 0; // 每次渲染之后 index = 0
ReactDOM.render(
<React.StrictMode>
<Counter></Counter>
</React.StrictMode>,
document.getElementById("root")
);
}
render();
源码
type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;
function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
return typeof action === 'function' ? action(state) : action;
}
export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return useReducer(
basicStateReducer,
// useReducer has a special case to support lazy useState initializers
(initialState: any),
);
}
以上源代码用Typescript标识了入参和返回参。useState是useReducer的语法糖。所以要看useState的源码,不妨先看useRecuder
useRecuder
基本用法
语法
const [state, dispatch] = useReducer(reducer, initialArg, init);
reducer和redux的reducer是一模一样的用法,接收state和action,返回state和当前匹配的dispatch的方式
示例
import React, { useReducer } from "react";
import ReactDOM from "react-dom";
const initialState = { count: 0 };
function counterReducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
let [state, dispatch] = useReducer(counterReducer, initialState);
console.log(state)
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
</div>
);
}
function render() {
ReactDOM.render(<Counter />, document.getElementById("root"));
}
render();
有很多state的时候可以用useReducer,而非useState,不然useState要写很多。
简易版的useReducer
// 以下是简易版的
let hookState = [];
let hookIndex = 0;
function useReducer(reducer, initialState) {
hookState[hookIndex] = hookState[hookIndex] || initialState;
let currentIndex = hookIndex;
function dispatch(action) {
console.log(action);
// 区分useState和useReducer的情况
hookState[currentIndex] = reducer
? reducer(hookState[currentIndex], action)
: action;
render();
}
return [hookState[hookIndex++], dispatch];
}
function useState(initialState) {
return useReducer(null, initialState);
}
// function Counter1() {
// let [number, setNumber] = useState(0);
// return (
// <div>
// <p>{number}</p>
// <button onClick={() => setNumber(number + 1)}>+</button>
// </div>
// );
// }
function Counter() {
let [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: "decrement" })}>+</button>
</div>
);
}
function render() {
hookIndex = 0;
ReactDOM.render(<Counter1 />, document.getElementById("root"));
}
render();
源码
源码基于Fiber写的,有点复杂。源码
后续计划
接下来的计划,useCallback与useMemo,这个是踩坑最多,因为一不留心,容易造成循环调用