1.Context(上下文) #

contextapi

1.1 使用 #

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
interface PageProps {
}
interface PageState {
    color: string
}
interface ContextValue {
    color: string;
    changeColor: (color: string) => void
}
let ThemeContext = React.createContext<ContextValue | null>(null);
let root: HTMLElement | null = document.querySelector('#root');
class Header extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}`, padding: 5 }}>
                            header
                          <Title />
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Title extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}` }}>
                            title
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Main extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}`, margin: 5, padding: 5 }}>
                            main
                        <Content />
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Content extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}`, padding: 5 }}>
                            Content
                            <button onClick={() => value!.changeColor('red')} style={{ color: 'red' }}>红色</button>
                            <button onClick={() => value!.changeColor('green')} style={{ color: 'green' }}>绿色</button>
                        </div>
                    )
                }
            </ThemeContext.Consumer>

        )
    }
}

class Page extends Component<PageProps, PageState> {
    constructor(props: PageProps) {
        super(props);
        this.state = { color: 'red' };
    }
    changeColor = (color: string) => {
        this.setState({ color });
    }
    render() {
        let contextVal: ContextValue = { changeColor: this.changeColor, color: this.state.color };
        return (
            <ThemeContext.Provider value={contextVal}>
                <div style={{ margin: '10px', border: `5px solid ${this.state.color}`, padding: 5, width: 200 }}>
                    page
                    <Header />
                    <Main />
                </div>
            </ThemeContext.Provider>

        )
    }
}
ReactDOM.render(<Page />, root);

1.2 实现 #

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
interface PageProps {
}
interface PageState {
    color: string
}
+interface ContextValue {
+    color: string;
+    changeColor: (color: string) => void
+}
+interface ContextProps<T> {
+    value: T
+}
+function createContext() {
+    let value;
+    function Provider(props) {
+        value = props.value;
+        Provider.value = value;
+        return props.children;
+    }
+    function Consumer(props) {
+        return props.children(value);;
+    }
+    return {
+        Provider,
+        Consumer
+    }
+}
+let ThemeContext = createContext<ContextValue | null>(null);
let root: HTMLElement | null = document.querySelector('#root');
class Header extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}`, padding: 5 }}>
                            header
                          <Title />
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Title extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}` }}>
                            title
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Main extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}`, margin: 5, padding: 5 }}>
                            main
                        <Content />
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}
class Content extends Component {
    render() {
        return (
            <ThemeContext.Consumer>
                {
                    (value: ContextValue | null) => (
                        <div style={{ border: `5px solid ${value!.color}`, padding: 5 }}>
                            Content
                            <button onClick={() => value!.changeColor('red')} style={{ color: 'red' }}>红色</button>
                            <button onClick={() => value!.changeColor('green')} style={{ color: 'green' }}>绿色</button>
                        </div>
                    )
                }
            </ThemeContext.Consumer>

        )
    }
}

class Page extends Component<PageProps, PageState> {
    constructor(props: PageProps) {
        super(props);
        this.state = { color: 'red' };
    }
    changeColor = (color: string) => {
        this.setState({ color });
    }
    render() {
        let contextVal: ContextValue = { changeColor: this.changeColor, color: this.state.color };
        return (
            <ThemeContext.Provider value={contextVal}>
                <div style={{ margin: '10px', border: `5px solid ${this.state.color}`, padding: 5, width: 200 }}>
                    page
                    <Header />
                    <Main />
                </div>
            </ThemeContext.Provider>

        )
    }
}
ReactDOM.render(<Page />, root);
class Header extends Component {
+    static contextType = ThemeContext
    render() {
+        this.context = Header.contextType.Provider.value;
        return (
            <div style={{ border: `5px solid ${this.context.color}`, padding: 5 }}>
                Header
                <Title />
            </div>
        )
    }
}

2. 高阶组件 #

const NewComponent = higherOrderComponent(OldComponent)

2.1 日志组件 #

import hoistNonReactStatics from 'hoist-non-react-statics';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
const logger = (WrappedComponent: React.FC) => {
    class LoggerComponent extends Component {
        start: number | null = null;
        componentWillMount() {
            this.start = Date.now();
        }
        componentDidMount() {
            console.log((Date.now() - this.start!) + 'ms')
        }
        render() {
            return <WrappedComponent />
        }
    }
    hoistNonReactStatics(LoggerComponent, WrappedComponent);
    return LoggerComponent;
}
let Hello = logger((props) => <h1>hello</h1>);
ReactDOM.render(<Hello />, document.getElementById('root'));

2.2 多层高阶组件 #

2.2.1 从localStorage中加载 #

localStorage.setItem('username','zhangsan');
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
interface WrappedComponentProps {
    value: string;
}

const fromLocal = (WrappedComponent: React.FC<WrappedComponentProps> | React.ComponentClass<WrappedComponentProps>) => {
    interface FromLocalComponentProps { //最终返回的组件的属性对象
        field: string
    }
    interface State {  //状态对象
        value: string;
    }
    class FromLocalComponent extends Component<FromLocalComponentProps, State> {
        constructor(props: FromLocalComponentProps) {
            super(props);
            this.state = { value: '' };
        }
        componentWillMount() {
            let value: string | null = localStorage.getItem(this.props.field);
            if (value)
                this.setState({ value });
        }
        render() {
            return <WrappedComponent value={this.state.value} />
        }
    }
    return FromLocalComponent;
}

const UserName = (props: WrappedComponentProps) => (
    <input defaultValue={props.value} />
)
const UserNameFromLocal = fromLocal(UserName);

ReactDOM.render(<UserNameFromLocal field="username" />, document.getElementById('root'));

2.2.2 从ajax中加载 #

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
interface WrappedComponentProps {
    value: string;
}

+const fromLocal = (WrappedComponent: React.ComponentClass<WrappedComponentProps>) => {
    interface FromLocalComponentProps { //最终返回的组件的属性对象
        field: string
    }
    interface State {  //状态对象
        value: string;
    }
    class FromLocalComponent extends Component<FromLocalComponentProps, State> {
        constructor(props: FromLocalComponentProps) {
            super(props);
            this.state = { value: '' };
        }
        componentWillMount() {
            let value: string | null = localStorage.getItem(this.props.field);
            if (value)
                this.setState({ value });
        }
        render() {
            return <WrappedComponent value={this.state.value} />
        }
    }
    return FromLocalComponent;
}
+const fromAjax = (WrappedComponent: React.FC<WrappedComponentProps>) => {
+    interface FromAjaxComponentProps { //最终返回的组件的属性对象
+        value: string
+    }
+    interface State {
+        value: string;
+    }

+    class FromAjaxComponent extends Component<FromAjaxComponentProps, State> {
+        constructor(props: WrappedComponentProps) {
+            super(props);
+            this.state = { value: '' };
+        }
+        componentDidMount() {
+            fetch(`/translate.json`).then(response => response.json()).then((data) => {
+                let value = data[this.props.value];
+                this.setState({ value });
+            });
+        }
+        render() {
+            return <WrappedComponent value={this.state.value} />
+        }
+    }
+    return FromAjaxComponent;
+}
+const UserName = (props: WrappedComponentProps) => (
+    <input defaultValue={props.value} />
+)
+const UserNameFromAjax = fromAjax(UserName);
+const UserNameFromLocal = fromLocal(UserNameFromAjax);

ReactDOM.render(<UserNameFromLocal field="username" />, document.getElementById('root'));

3. render props #

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

3.1 原生实现 #

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
interface Props {

}
interface State {
    x: number;
    y: number;
}
class MouseTracker extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = { x: 0, y: 0 };
    }

    handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    }

    render() {
        return (
            <div onMouseMove={this.handleMouseMove}>
                <h1>移动鼠标!</h1>
                <p>当前的鼠标位置是 ({this.state.x}, {this.state.y})</p>
            </div>
        );
    }
}
ReactDOM.render(<MouseTracker />, document.getElementById('root'));

3.2 children #

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

interface State {
    x: number;
    y: number;
}
interface Props {
    children: (state: State) => React.ReactNode
}
class MouseTracker extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = { x: 0, y: 0 };
    }

    handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    }

    render() {
        return (
            <div onMouseMove={this.handleMouseMove}>
                {this.props.children(this.state)}
            </div>
        );
    }
}
ReactDOM.render(<MouseTracker >
    {
        (props: State) => (
            <>
                <h1>移动鼠标!</h1>
                <p>当前的鼠标位置是 ({props.x}, {props.y})</p>
            </>
        )
    }
</MouseTracker >, document.getElementById('root'));

3.3 render属性 #

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
interface Props {
    render: (state: State) => React.ReactNode
}
interface State {
    x: number;
    y: number;
}
class MouseTracker extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = { x: 0, y: 0 };
    }

    handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    }

    render() {
        return (
            <div onMouseMove={this.handleMouseMove}>
                {this.props.render(this.state)}
            </div>
        );
    }
}

ReactDOM.render(< MouseTracker render={params => (
    <>
        <h1>移动鼠标!</h1>
        <p>当前的鼠标位置是 ({params.x}, {params.y})</p>
    </>
)} />, document.getElementById('root'));

3.4 HOC #

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
interface Props {
    render: (state: State) => React.ReactNode
}
interface State {
    x: number;
    y: number;
}
class MouseTracker extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = { x: 0, y: 0 };
    }

    handleMouseMove = (event: React.MouseEvent<HTMLDivElement>) => {
        this.setState({
            x: event.clientX,
            y: event.clientY
        });
    }

    render() {
        return (
            <div onMouseMove={this.handleMouseMove}>
                {this.props.render(this.state)}
            </div>
        );
    }
}
+function withMouse(Component: React.FC<State>) {
+    return (
+        (props: State) => <MouseTracker render={mouse => <Component {...props} {...mouse} />} />
+    )
+}
+let App = withMouse((props: State) => (
+    <>
+        <h1>移动鼠标!</h1>
+        <p>当前的鼠标位置是 ({props.x}, {props.y})</p>
+    </>
+));
ReactDOM.render(<App />, document.getElementById('root'));