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 作为一个高阶组件 实现
AddOn
和Slot
两组件的数据流转 这里选择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.Component
和 typeof 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 // 正确