react + ts 、react hooks学习笔记

create-react-app

  • 项目是用cli生成的, 目录默认不会有webpack配置项
  • 但是我们有需要对其进行改造, 比如要用 less 啊等
  • 这个时候要运行 npm run eject 将所有配置文件给暴露出来 (操作不可逆)
  • 然后根据自己的需要,下载 loader 修改配置

定义react组件 props 和 state 的类型

react1.jpg

子组件向父组件输出值

  • 父组件向子组件内部输入一个事件, 子组件触发这个事件,并传参
import * as React from 'react';
import './App.less';
class ChildCom extends React.Component<IChildComProps> {
  constructor(props: any) {
    super(props);
    this.change = this.change.bind(this);
  }
  change(e: any) {
    this.props.change(e.target.value);
  }
  render() {
    return <input onChange={this.change} type="text"/>
  }
}
class App extends React.Component<any, IAppComState> {
  constructor(props: any) {
    super(props);
    this.state = {
      value: ''
    }
    this.change = this.change.bind(this)
  }
  public render() {
    return (
      <div className="app">
          <ChildCom change={this.change}/>
          {this.state.value}
      </div>
    );
  }
  change(e: string) {
    this.setState({
      value: e
    })
  }
}

interface IChildComProps extends React.Props<any>{
  change: (v: any) => void;
}
interface IAppComState{
  value: string;
}
export default App;

路由

  • error-info: you should not use link outside a router
  • react-router-dom 要求路由的匹配(Route)和 路由的跳转(Link) 都要写在 Router里面
  • 所以可将我们的整个应用 都包裹在Router里面

做法如下:

router.jpg

route文件.jpg
<Switch>
        <Route exact path='/' component={Home} />    // 设置默认路由
        <Route path='/home' component={Home} />
        <Route path='/find' component={Find} />
        <Route path='/recommend' component={Recommend} />
        <Route path='/rank' component={Rank} />
 </Switch>
  • exact 精确匹配 如果不加上此标志 所有路由匹配都会匹配到 / (表现是路由跳转老是默认路由)

动态的打开组件(适用弹窗,消息提醒等)

@Injectable()
export class UDynamicService {

    private nameDivMap: Map<string, Element[]> = new Map();
    private divNameMap: Map<Element, string> = new Map();

    open<CP>(options: DynamicHelperOptions<CP>) {
        const Component = options.component;
        const name = Component.name;
        const div = document.createElement('div');
        ReactDOM.render((
            <Component id={div} {...options.props} />
        ), div);
        if (options.selector) {
            options.selector.appendChild(div);
        } else {
            document.body.appendChild(div);
        }
        const arrDiv: Element[] = (this.nameDivMap.get(name) || []);
        arrDiv.push(div);
        this.nameDivMap.set(name, arrDiv);
        this.divNameMap.set(div, name);
        return div;
    }
    private destroyedEleAndCom(ele: Element) {
        ReactDOM.unmountComponentAtNode(ele);
        ele.remove();
    }
    private removeDivFormMap(name: string, ele: Element) {
        this.divNameMap.delete(ele);
        const divArr = this.nameDivMap.get(name) || [];
        if (divArr.length > 0) {
            removeFromArrayByCondition(divArr, (item: Element) => {
                return item === ele;
            });
        }
    }
    destroyed(ele: Element, isDeleteOtherSameCom?: boolean) {
        setTimeout(() => {
            const name = this.divNameMap.get(ele);
            if (isDeleteOtherSameCom) {
                const divArr = this.nameDivMap.has(name) ? this.nameDivMap.get(name) : [];
                divArr.forEach(item => {
                    this.destroyedEleAndCom(item);
                    this.removeDivFormMap(name, item);
                });
            } else {
                this.destroyedEleAndCom(ele);
                this.removeDivFormMap(name, ele);
            }
        }, 300);
    }
}

interface DynamicHelperOptions<CP> {
    // 默认id为 displayName, 但是有种很特殊的情况, 一个组件要打开两次
    // id?: any,
    // eslint-disable-next-line no-undef
    component: (props: CP) => JSX.Element; // 要打开的组件
    props?: CP;
    selector?: HTMLDivElement; // 在哪打开它
    isDeleteOtherSameCom?: boolean; // 要删除已经打开的相同的displayName的组件吗
}

利用 context 实现类似Vue的插槽分发组件

  • 先看使用
// TestSlot.tsx
import React from 'react';
import { Slot } from './Slot';
import { SlotProvider } from './slotProvider';
class TestSlot extends React.Component {
    render() {
        return (
            <div>
                雅哈哈哈雅哈哈哈雅哈哈哈雅哈哈哈
                <Slot name='header'></Slot>
                <Slot></Slot>
                <Slot name='footer'></Slot>
            </div>
        )
    }
}
export default SlotProvider(TestSlot)

// app.tsx
import React from 'react';
import { AddOn } from './component/TestSlot/AddOn';
import SlotProvider from './component/TestSlot/TestSlot';
function App() {
  return (
    <div className="App">
      <SlotProvider>
        <AddOn slot='header'>headerheaderheader</AddOn>
        <AddOn>bodyodyodyodyody</AddOn>
        <AddOn slot='footer'>fotterfotterfotterfotterfotter</AddOn>
      </SlotProvider>
    </div>
  );
}

export default App;

  • 实现思路

SlotProvider 作为一个高阶组件 实现 AddOnSlot两组件的数据流转 这里选择 context

AddOn 组件作为中间层 本身其实啥也不干,它的作用就是为SlotProvider提供插槽数据

Slot 组件其实就是根据自身的 slot name去获取渲染的内容了

  • 实现如下
// slotProvider.tsx 
import React, { ReactNode } from 'react';
import { AddOn } from './AddOn';

export const SlotContext = React.createContext<{[props: string]: ReactNode}>({
  addOnRenders: {}  // 用 slotName: 渲染内容(children)的形式存放渲染数据
})

export const SlotProvider = (WrapperComponent: typeof React.Component) => {
    return class extends React.Component {
        addOnRenders: {[props: string]: ReactNode} = {}
        render() {
            // addOn 作为 SlotProvider 的子组件 这里 把它铺平为数组 并 存放到 addOnRenders 里面
            let addOnArrs: AddOn[]  = React.Children.toArray(this.props.children) as any
            if (addOnArrs && addOnArrs.length > 0) {
                addOnArrs.forEach(item => {
                    let key: string = item.props.slot || '$$default'  // default 作为默认值
                    this.addOnRenders[key] = item.props.children
                })
            }
            return (
                <SlotContext.Provider value={this.addOnRenders as any}>
                    <WrapperComponent {...this.props}/>
                </SlotContext.Provider>
            )
        }
    }
}
// AddOn.tsx
import React from 'react';
export class AddOn extends React.Component<AddOnPropsObj> {
    constructor(props: AddOnPropsObj) {
        super(props)
    }
    render() {
        return null
    }
}

type AddOnPropsObj = {slot?: string}
// Slot.tsx
import React from 'react';
import { SlotContext } from './slotProvider';

export class Slot extends React.Component<SlotPropsObj> {
    // 渲染 AddOn 缓存的 children
    render() {
        return (
            <SlotContext.Consumer>
                {(context) => {
                    // console.log(context, this.props)
                    let renderContent = context[this.props.name || '$$default']
                    return renderContent || null
                }}
            </SlotContext.Consumer>
        )
    }
}
type SlotPropsObj = {
    name?: string
}
1584090281595.jpg

类型定义 相关

1、 类型 React.Componenttypeof React.Component 又什么区别

typeof React.Component 指的是 React.Component 类型

直接使用 React.Component 指的是 React.Component 实例 类型

class Test {
  static aa() {}
  bb() {}
}
let a: Test
a = new Test()   // 正确
// a = Test  // 报错

let b: typeof Test
// b = new Test()  // 报错
b = Test   // 正确

项目地址 (https://github.com/liuxinya/react-ts

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355