react + ts 、react hooks學(xué)習(xí)筆記

create-react-app

  • 項(xiàng)目是用cli生成的, 目錄默認(rèn)不會(huì)有webpack配置項(xiàng)
  • 但是我們有需要對(duì)其進(jìn)行改造, 比如要用 less 啊等
  • 這個(gè)時(shí)候要運(yùn)行 npm run eject 將所有配置文件給暴露出來(lái) (操作不可逆)
  • 然后根據(jù)自己的需要,下載 loader 修改配置

定義react組件 props 和 state 的類(lèi)型

react1.jpg

子組件向父組件輸出值

  • 父組件向子組件內(nèi)部輸入一個(gè)事件, 子組件觸發(fā)這個(gè)事件,并傳參
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)和 路由的跳轉(zhuǎn)(Link) 都要寫(xiě)在 Router里面
  • 所以可將我們的整個(gè)應(yīng)用 都包裹在Router里面

做法如下:

router.jpg

route文件.jpg
<Switch>
        <Route exact path='/' component={Home} />    // 設(shè)置默認(rèn)路由
        <Route path='/home' component={Home} />
        <Route path='/find' component={Find} />
        <Route path='/recommend' component={Recommend} />
        <Route path='/rank' component={Rank} />
 </Switch>
  • exact 精確匹配 如果不加上此標(biāo)志 所有路由匹配都會(huì)匹配到 / (表現(xiàn)是路由跳轉(zhuǎn)老是默認(rèn)路由)

動(dòng)態(tài)的打開(kāi)組件(適用彈窗,消息提醒等)

@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> {
    // 默認(rèn)id為 displayName, 但是有種很特殊的情況, 一個(gè)組件要打開(kāi)兩次
    // id?: any,
    // eslint-disable-next-line no-undef
    component: (props: CP) => JSX.Element; // 要打開(kāi)的組件
    props?: CP;
    selector?: HTMLDivElement; // 在哪打開(kāi)它
    isDeleteOtherSameCom?: boolean; // 要?jiǎng)h除已經(jīng)打開(kāi)的相同的displayName的組件嗎
}

利用 context 實(shí)現(xiàn)類(lèi)似Vue的插槽分發(fā)組件

  • 先看使用
// 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;

  • 實(shí)現(xiàn)思路

SlotProvider 作為一個(gè)高階組件 實(shí)現(xiàn) AddOnSlot兩組件的數(shù)據(jù)流轉(zhuǎn) 這里選擇 context

AddOn 組件作為中間層 本身其實(shí)啥也不干,它的作用就是為SlotProvider提供插槽數(shù)據(jù)

Slot 組件其實(shí)就是根據(jù)自身的 slot name去獲取渲染的內(nèi)容了

  • 實(shí)現(xiàn)如下
// slotProvider.tsx 
import React, { ReactNode } from 'react';
import { AddOn } from './AddOn';

export const SlotContext = React.createContext<{[props: string]: ReactNode}>({
  addOnRenders: {}  // 用 slotName: 渲染內(nèi)容(children)的形式存放渲染數(shù)據(jù)
})

export const SlotProvider = (WrapperComponent: typeof React.Component) => {
    return class extends React.Component {
        addOnRenders: {[props: string]: ReactNode} = {}
        render() {
            // addOn 作為 SlotProvider 的子組件 這里 把它鋪平為數(shù)組 并 存放到 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 作為默認(rèn)值
                    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

類(lèi)型定義 相關(guān)

1、 類(lèi)型 React.Componenttypeof React.Component 又什么區(qū)別

typeof React.Component 指的是 React.Component 類(lèi) 類(lèi)型

直接使用 React.Component 指的是 React.Component 實(shí)例 類(lèi)型

class Test {
  static aa() {}
  bb() {}
}
let a: Test
a = new Test()   // 正確
// a = Test  // 報(bào)錯(cuò)

let b: typeof Test
// b = new Test()  // 報(bào)錯(cuò)
b = Test   // 正確

項(xiàng)目地址 (https://github.com/liuxinya/react-ts

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容