React.js 高级用法
npx create-react-app my-app --template typescript
一、高阶组件用法及封装
什么是高阶组件
Hoc, High Order Component
a higher-component is a function that takes a component and returns a new component.
高阶组件是一个函数
入参: 是一个 react 组件
出参: 是一个新的 react 组件
先来写一个高阶函数
function getName(name) {
// 业务逻辑
return name;
}
function helloWorld(name) {
// 自己的行为
console.log(`hello, beautiful world, my name is ${name}`);
}
function byeWorld(name) {
// 自己的行为
console.log(`bye, ugly world, my name is ${name}`);
}
/**
* 入参: 函数
* 出参: 函数
* @param {*} wrappedFunc
*/
const wrapWithUsername = wrappedFunc => () => {
const name = getName('qiuku');
wrappedFunc(name);
};
wrapWithUsername(helloWorld)();
wrapWithUsername(byeWorld)();怎样写一个高阶组件
export const newComponent = hoc(WrappedComponent);- 普通方式
/* hoc/index.tsx */
import React, { Component } from "react";
interface State {
name: string;
}
export const wrapWithUserName = (WrappedComponent: any) => {
return class extends Component<any, any> {
public state: State = {
name: ""
};
componentWillMount() {
const username = localStorage.getItem("name");
this.setState({
name: username
});
}
render(): React.ReactNode {
return <WrappedComponent name={this.state.name} {...this.props} />
}
}
}
/* high/HelloWorld.tsx */
import React, {Component} from "react";
import { wrapWithUserName } from "../hoc";
interface Props {
name: string;
}
class HelloWorld extends Component<Props, any> {
render(): React.ReactNode {
return <div>hello world, my name is {this.props.name}</div>
}
}
export default wrapWithUserName(HelloWorld);
/* high/ByeWorld.tsx */
import React, {Component} from "react";
import { wrapWithUserName } from "../hoc";
interface Props {
name: string;
}
class ByeWorld extends Component<Props, any> {
render(): React.ReactNode {
return <div>bye, ugly world, my name is {this.props.name}</div>
}
}
export default wrapWithUserName(ByeWorld);- 装饰器
import React, { Component } from "react";
import { wrapWithUserName } from "../hoc";
interface Props {
name?: string;
}
@wrapWithUserName
class ByeWorld extends Component<Props, any> {
render(): React.ReactNode {
return <div>再见 世界, my name is {this.props.name}</div>
}
}
export default ByeWorld;- 多个高阶函数组合
/* hoc/index.tsx */
import React, { Component } from "react";
interface State {
name: string;
}
export const wrapWithUserName = (WrappedComponent: any) => {
return class extends Component<any, any> {
public state: State = {
name: ""
};
componentWillMount() {
const username = localStorage.getItem("name");
this.setState({
name: username
});
}
render(): React.ReactNode {
return <WrappedComponent name={this.state.name} {...this.props} />
}
}
}
export const wrapWithHeight = (height?: number) => {
return (WrappedComponent: any) => {
return class extends Component<any, any> {
render(): React.ReactNode {
return (
<div>
我的身高是{height}
<WrappedComponent {...this.props} />
</div>
)
}
}
}
}
/* muti-high/HelloWorld.tsx */
import React, { Component } from "react";
import { wrapWithHeight, wrapWithUserName } from "../hoc";
interface Props {
name?: string;
}
@wrapWithUserName
@wrapWithHeight(180)
class HelloWorld extends Component<Props, any> {
render(): React.ReactNode {
return <div>你好 世界, my name is {this.props.name}</div>
}
}
export default HelloWorld;
// export default wrapWithUserName(wrapWithHeight(170)(HelloWorld));高阶组件能用来做什么
属性代理
1.1 操作props
1.2 操作组件实例
import React, { Component } from "react";
export const refHoc = (WrappedComponent: any) => class extends Component<any, any> {
ref: any = null;
componentDidMount() {
console.log(this.ref);
// 子组件 componentDidMount
// 父组件 componentDidMount
}
render(): React.ReactNode {
return (
<div>
<WrappedComponent {...this.props} ref={(instance: any) => (this.ref = instance)} />
</div>
);
}
};继承/劫持
2.1 通过继承入参 component 的方式, 可以拿到原 component 的任意属性或者方法.
2.2 可以任意改变 render
/* hijack/index.tsx */
import React, {Component} from "react";
import {hijackHoc} from "../hoc/hijackHoc";
interface Props {
name?: string;
}
interface State {
weight?: number;
height?: number;
}
@hijackHoc
class HijackComponent extends Component<Props, State> {
constructor(p: Readonly<Props> | Props, context: State) {
super(p);
React.createContext(this.state);
this.handleClick = this.handleClick.bind(this);
}
state: State = {
weight: 70,
height: 190
};
handleClick() {
this.setState({
weight: this.state.weight! + 1
});
}
render(): React.ReactNode {
return (
<div onClick={this.handleClick}>
hello world, my name is {this.state.weight}
</div>
)
}
}
export default HijackComponent;
/* hajackHoc.tsx */
import React from "react";
/* 注意这里继承的是注解的 class */
export function hijackHoc<T extends { new(...args: any[]): any }>(component: T) {
return class extends component {
// 继承 劫持
handleClick = () => {
super.handleClick();
alert('你被我劫持啦!');
}
render() {
const parent = super.render();
return React.cloneElement(parent, {
onClick: this.handleClick
});
}
}
}二、react hooks
什么是 react hooks
hook -> 钩子, 16.8 新特性, 不写 class 的情况下使用 state 和其他 react 特性.
useXXX, useState, useEffect
class -> hooks
react hooks 有什么优势
先来看一下 class 写组件有什么不足之处吧!
组件间的状态逻辑很难复用 -> 后期 react 提供了高阶组件的形式解决这个问题
复杂业务的有状态组件会越来越复杂
通过更改 this.state 来实现各种业务逻辑
但是 class 里有很多很多的生命周期, 更改 state 的逻辑散布在四面八方
willMount -> timer setInterval
clearInterval
this 指向问题
import React, { Component } from "react";
/**
* 4 种监听事件的 this 指向方式
*/
class Test extends Component<any, any> {
private handleClick2: OmitThisParameter<() => void>;
constructor(props: any) {
super(props);
this.state = {
num: 1,
title: '11111'
};
this.handleClick2 = this.handleClick1.bind(this);
}
handleClick1() {
this.setState({
num: this.state.num + 1
});
}
handleClick4 = () => this.setState({
num: this.state.num + 1
});
render() {
return (
<div>
{/* 由于 bind 每次都会返回新韩淑, 所以子组件永远都会随着父组件刷新 */}
<button onClick={this.handleClick1.bind(this)}>btn1</button>
{/* 不会造成性能问题 */}
<button onClick={this.handleClick2}>btn2</button>
<button onClick={() => this.handleClick1()}>btn3</button>
<button onClick={this.handleClick4}>btn4</button>
</div>
);
}
}而 hooks 的优点, 一对比就比较明显了
能优化类组件的三大问题
能在无需修改组件结构的情况下复用状态逻辑(自定义 Hooks)
能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
副作用的概念
没有发生数据向视图转换过程中的逻辑被称为副作用.
ajax, 绑定/解绑时间, 订阅/取消订阅
useEffect 会在 全部渲染完毕之后才执行, 比如绑定/解绑事件可以聚集到一个 useEffect 里了.
react hooks 的注意事项
只能在函数内部的最外层调用 Hook, 不要在循环、条件判断或者子函数中调用
react 的 useState 内部是通过单链表存储的, 必须保证顺序.
只能在 React 的函数组件中调用 Hook, 不要在其它 JavaScript 函数中调用
useEffect 第二个参数传空数组, 就相当于 didMount?
react hooks 是怎么实现的
- useState
简易实现
import React from "react";
import ReactDOM from "react-dom";
/*
* 1. 传入一个初始值, 返回一个状态值和一个改变状态值的方法
*
*/
export function OriginCounter() {
cursor = 0; // 下标归零
const [count, setCount] = useState<number>(0);
const [name, setName] = useState<string>('哈哈哈');
const onClick = () => setCount(count + 1);
const onClickName = () => setName('hahaha' + Math.random());
return (
<div>
<div>{count}</div>
<button onClick={onClick}>点击+1</button>
<div>{name}</div>
<button onClick={onClickName}>点击修改name</button>
</div>
);
}
/* 这种实现方式, 只允许调用一次 useState */
// let state: any;
/*
* 为什么 ? 只能在函数内部的最外层调用 Hook, 不要再循环、条件判断或者子函数中调用
*
* 单链表 next => state
*/
let stateArray: any[] = [];
let cursor: number = 0;
function useState<T>(initialState: T): [T, (newState: T) => void] {
// state = state || initialState;
// function setState(newState: T) {
// state = newState;
// render();
// }
// return [state, setState];
const currentCursor = cursor;
stateArray[currentCursor] = stateArray[currentCursor] || initialState;
function setState(newState: T) {
stateArray[currentCursor] = newState;
render();
}
cursor++;
return [stateArray[currentCursor], setState];
}
export function render() {
ReactDOM.render(<OriginCounter/>, document.getElementById('root'));
}- useEffect
简易实现
import React, { useState } from 'react';
import ReactDOM from "react-dom";
export function CounterEffect() {
effectCursor = 0;
const [count, setCount] = useState(0);
// didMount, 比如有的逻辑只需要执行一次 deps 置空
useEffect(() => console.log(count), [count]); // 1. 回调, 2. 依赖项
useEffect(() => console.log('hahaha'), [count]);
const onClick = () => setCount(count + 1);
return (
<div>
<div>{count}</div>
<button onClick={onClick}>点击+1</button>
</div>
);
}
/*
* 1. 有两个参数, 第一个是 callback, 第二个是依赖项数组
* 2. 如果依赖项为空数组, 那么只执行一次 callback
* 3. 如果依赖项存在, 那么每当它发生了变化, callback 就会执行
* 4. 如果不存在第二个参数, 那么每次渲染都会执行 callback
*/
// let _deps: any[] | undefined = undefined; // 用来记录 uesEffect 上一次的依赖项
let _allDeps: Array<any[] | undefined> = []; // 用来记录多个 uesEffect 上一次的依赖项
let effectCursor: number = 0;
function useEffect(callback: () => void, deps?: any[]) {
if (!deps) {
// 如果没有传入依赖项
callback();
// _deps = deps;
_allDeps[effectCursor] = deps;
effectCursor++;
return;
}
const _deps = _allDeps[effectCursor]; // 当前 useEffect 上一次记录的依赖项
const hasChangedDeps = _deps ? deps.some((current, i) => current !== _deps![i]) : true;
if (hasChangedDeps) {
callback();
// _deps = deps;
_allDeps[effectCursor] = deps;
}
effectCursor++;
}
export function render() {
ReactDOM.render(<CounterEffect/>, document.getElementById('root'));
}react hooks 用法详解
useState
useEffect
useMemo
为了性能优化而存在的一个 hook, 把创建的函数和依赖项数组作为参数, 它仅仅会在依赖项发生改变的时候, 重新计算第一个参数的值.
tsximport React, {useState, useMemo} from "react"; function SubCounter(props: { onClick: () => void; data: { value: number}}) { console.log("子组件渲染"); const {onClick, data} = props; return ( <button onClick={onClick}>{data.value}</button> ); } export default function MemoCounter() { console.log("父组件渲染"); const [name, setName] = useState<string>("计数器"); const [count, setCount] = useState(0); const data = {value: count}; const addClick = () => setCount(count + 1); /* useMemo 使子组件只在 data.value 改变的时候重新渲染 */ /* 这里第一个参数可以不直接返回组件, 可以返回其他 */ const MemoSubCounter = useMemo( () => (<SubCounter data={data} onClick={addClick} />), [count] ); return ( <div> <input type="text" value={name} onChange={(e) => setName(e.target.value)}/> <p>{data.value}</p> {MemoSubCounter} </div> ) }useReducer
useReducer + useContext 讲一下模拟 useRedux, 放在 react 实战
useDef
自定义 hook useInterval
react 实战
自定义 redux
react 实战