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
,这个是踩坑最多,因为一不留心,容易造成循环调用