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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。