React(一):入门React一篇文章就够了 - React + Router + Mobx + Antd

项目构建

$ npm init vite@latest
$ npm install uuid
$ npm i antd
$ npm install react-router-dom@6
$ npm install mobx mobx-react-lite

解决Could not find a declaration file for module 'uuid'找不到js脚本问题

  • src/vite-env.d.ts 添加以下代码
    declare module 'uuid';
    

入口渲染根组件修改

// 核心依赖
import React from 'react';
// 渲染依赖
import ReactDOM from 'react-dom/client';
// 入口组件
import App from './App';
// 全局样式
import './index.css';

/**
 * 渲染根组件
 *      React.StrictMode 需要去掉严格模式
 *      原因:严格模式影响 useEffect 的执行时机
 * ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
 *      <React.StrictMode>
 *          <App/>
 *      </React.StrictMode>
 * );
 */
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App/>);

数组渲染

import {v4 as uuidv4} from 'uuid';

const list = [
    {id: uuidv4(), name: 'Lee', age: 18},
    {id: uuidv4(), name: 'Tom', age: 19},
    {id: uuidv4(), name: 'Lucy', age: 20},
]

function App() {

    return (
        <div className="App">
            <ul>
                {
                    list.map(item => <li key={item.id}>{item.name} ---&gt; {item.age} </li>)
                }
            </ul>
        </div>
    )
}

export default App

条件渲染

可通过条件表达式三元运算符或者方法进行判断

const getName = () => {
    switch (Math.floor(Math.random() * 3 + 1)) {
        case 1:
            return 'Lee';
        case 2:
            return 'Tom';
        case 3:
            return '张三';
    }
}

function App() {
    return (
        <div className="App">
            {/* 条件表达式 */}
            {true || <span>Lee</span>}
            {/* 三元运算符 */}
            {true ? 'Lee' : <span>Tom</span>}
            {/* 方法 */}
            {getName()}
        </div>
    )
}

export default App

样式处理

import './App.css'; // .c-blue { color: blue; }

const styleObj: { [propName: string]: string } = {
    color: 'red',
    textAlign: 'center'
}

function App() {
    return (
        <div className="App">

            {/* 行内样式 */}
            <p style={{color: true ? '#00d0ff' : '#ff0000', textAlign: 'center'}}>Hello Lee!!!</p>
            <p style={styleObj}>Hello Lee!!!</p>

            {/* 外部样式 */}
            <p className={true && 'c-blue'}>Hello Tom!!!</p>

        </div>
    )
}

export default App

幽灵节点

组件必须存在根节点,如果不想定义确切的根节点可以用 <></> 幽灵节点来代替

function App() {
    return (
        <>
            <span>Hello Lee,</span>
            <span>Tom !!!</span>
        </>
    )
}

export default App

事件绑定及手动渲染页面

三种事件绑定方式

import React, {BaseSyntheticEvent} from "react";

class App extends React.Component {

    private name: string = '';

    render() {
        return (
            <>
                <h1>{this.name}</h1>
                <button onClick={(event) => this.modifyName1('Tom', event)}>修改NAME</button>
                <button onClick={this.modifyName2.bind(this, 'Lucy')}>修改NAME</button>
                <button onClick={this.modifyName3}>修改NAME</button>
            </>
        )
    }

    modifyName1(name: string, e: BaseSyntheticEvent): void {
        this.name = name;
        this.forceUpdate();
        console.log(this, e)
    }

    modifyName2(name: string, e: BaseSyntheticEvent): void {
        this.name = name;
        this.forceUpdate();
        console.log(this, e)
    }

    modifyName3 = (e: BaseSyntheticEvent): void => {
        this.name = 'Lee';
        this.forceUpdate();
        console.log(this, e)
    }

}

export default App

修改 state 中数据自动渲染页面

import {v4 as uuidv4} from 'uuid';
import React, {BaseSyntheticEvent} from "react";

interface Info {
    name: string,
    age: number
}

interface UserItem {
    id: string,
    info: Info
}

class App extends React.Component {

    state = {
        person: {
            name: 'Lee',
            age: 18
        },
        list: [
            {id: uuidv4(), info: {name: 'Lee', age: 18}}
        ],
    }

    render() {
        return (
            <>
                <h1>{this.state.person.name} - {this.state.person.age}</h1>
                <input type="text" value={this.state.person.name}
                       onChange={this.handlerChangePersonName.bind(this)}/>

                <hr/>

                <button onClick={this.addRow.bind(this)}>新增</button>
                <ul>
                    {
                        this.state.list.map((item, index) =>
                            (
                                <li key={item.id}>
                                    <input type="text" value={item.info.name}
                                           onChange={this.handlerChangeName.bind(this, item, index)}/>
                                    <input type="text" value={item.info.age}
                                           onChange={this.handlerChangeAge.bind(this, item, index)}/>
                                    <button onClick={this.delRow.bind(this, index)}>删除</button>
                                </li>
                            ))
                    }
                </ul>
                <button onClick={this.getStateData.bind(this)}>确定</button>
            </>
        )
    }

    // 修改名字
    handlerChangePersonName(e: BaseSyntheticEvent): void {
        this.setState({
            person: {
                ...this.state.person, // 需要保留之前的属性,否则age属性将会消失
                name: e.target.value
            }
        })
    }

    // 添加行
    addRow(): void {
        this.setState({
            list: [
                ...this.state.list,
                {
                    id: uuidv4(),
                    info: {
                        name: '',
                        age: 0
                    }
                }
            ]
        })
    }

    // 删除行
    delRow(index: number): void {
        this.state.list.splice(index, 1);
        this.forceUpdate();
    }

    // 名字监听输入
    handlerChangeName(item: UserItem, index: number, e: BaseSyntheticEvent): void {
        item.info.name = e.target.value;
        this.setState({
            list: this.state.list
        });
    }

    // 年龄监听输入
    handlerChangeAge(item: UserItem, index: number, e: BaseSyntheticEvent): void {
        const age = Number(e.target.value);
        if (isNaN(age)) {
            return console.log('请输入数字!!!');
        }
        item.info.age = age;
        this.setState({
            list: this.state.list
        });
    }

    // 获取全部数据
    getStateData() {
        console.log(this.state);
    }
}

export default App

表单

受控表单组件

不可直接输入内容需要添加onChange事件

import React, {BaseSyntheticEvent} from "react";

class App extends React.Component {

    state = {
        value: ''
    }

    render() {
        return (
            <>
                <input type="text" value={this.state.value} onChange={this.handlerChange.bind(this)}/>
            </>
        )
    }

    handlerChange(e: BaseSyntheticEvent) {
        const {value} = e.target;
        this.setState({value});
    }
}

export default App

非受控表单组件

可直接输入内容,在其他方法中获取表单输入内容

import React, {createRef} from "react";

class App extends React.Component {

    private valueRef = createRef<HTMLInputElement>();

    state = {
        value: ''
    }

    render() {
        return (
            <>
                <input type='text' ref={this.valueRef}/>
                <span>{this.state.value}</span>
                <button onClick={this.getInputValue.bind(this)}>获取VALUE</button>
            </>
        )
    }

    getInputValue() {
        console.log(this.valueRef);
        this.setState({
            value: this.valueRef.current!.value
        })
    }
}

export default App

组件通信

  • 组件约定说明
    1. 组件名首字母必须大写
    2. 函数组件必须有返回值
    3. 组件名和函数名一致可进行标签自闭和

父传子

props 为只读属性,不能进行修改

props 能传递任何数据(包括函数和tsx)

在这里插入图片描述

  • 父组件

    import React, {BaseSyntheticEvent} from "react";
    import ComponentA from "./components/ComponentA/ComponentA";
    
    // 传过去的组件
    class From_App_to_ComponentA_tsx extends React.Component {
        render() {
            return (
                <>
                    <span style={{border: '1px solid #0000FF', padding: '0 10px'}}>From_App_to_ComponentA_tsx</span>
                </>
            )
        }
    }
    
    class App extends React.Component<any, any> {
    
        state = {
            from_App_to_ComponentA: {
                message: '由根组件 App 传给 ComponentA 组件的 message 字符串',
                fun: () => '由根组件 App 传给 ComponentA 组件的 fun 函数',
                node: (<span>由根组件 App 传给 ComponentA 组件的 节点 标签</span>),
                tsx: From_App_to_ComponentA_tsx
            }
        }
    
        render() {
            return (
                <div className="app" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h1>App</h1>
                    <input type="text" value={this.state.from_App_to_ComponentA.message}
                           onChange={this.handlerChange_From_App_to_ComponentA_message.bind(this)}/>
                    <ComponentA {...this.state.from_App_to_ComponentA}/>
                </div>
            )
        }
    
        // 监听输入框
        handlerChange_From_App_to_ComponentA_message(e: BaseSyntheticEvent) {
            this.setState({
                from_App_to_ComponentA: {
                    ...this.state.from_App_to_ComponentA,
                    message: e.target.value
                }
            })
        }
    
    }
    
    export default App
    
  • 子组件

    import React from "react";
    
    class ComponentA extends React.Component<any, any> {
        render() {
            console.log(this.props.tsx)
            return (
                <div className="component-a" style={{border: '1px solid #FF00FF', padding: '20px', margin: '20px'}}>
                    <h2>Component A</h2>
                    <p>From App To ComponentA Message : {this.props.message}</p>
                    <p>From App To ComponentA Function : {this.props.fun()}</p>
                    <p>From App To ComponentA Node : {this.props.node}</p>
                    <p>From App To ComponentA Tsx : <this.props.tsx/></p>
                </div>
            )
        }
    }
    
    export default ComponentA;
    

子传父

子组件调用父组件传过来的函数

在这里插入图片描述

  • 父组件

    import React from "react";
    import ComponentA from "./components/ComponentA/ComponentA";
    
    class App extends React.Component<any, any> {
    
        state = {
            from_ComponentA_to_App_message: ''
        }
    
        render() {
            return (
                <div className="app" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h1>App</h1>
                    <p>{this.state.from_ComponentA_to_App_message}</p>
                    <ComponentA from_App_to_ComponentA_fun={this.setMessage.bind(this)}/>
                </div>
            )
        }
    
        // 设置数据
        setMessage(msg: string) {
            this.setState({
                from_ComponentA_to_App_message: msg
            })
        }
    }
    
    export default App
    
  • 子组件

    import React, {BaseSyntheticEvent} from "react";
    
    class ComponentA extends React.Component<any, any> {
        render() {
            return (
                <div className="component-a" style={{border: '1px solid #FF00FF', padding: '20px', margin: '20px'}}>
                    <h2>Component A</h2>
                    <input type="text" onChange={this.handlerChange.bind(this)}/>
                </div>
            )
        }
    
        // 监听输入
        handlerChange(e: BaseSyntheticEvent) {
            this.props.from_App_to_ComponentA_fun(e.target.value)
        }
    }
    
    export default ComponentA;
    

兄弟组件

利用父组件进行传参

在这里插入图片描述

  • 父组件

    import React from "react";
    import ComponentA from "./components/ComponentA/ComponentA";
    import ComponentB from "./components/ComponentB/ComponentB";
    
    class App extends React.Component<any, any> {
    
        state = {
            value: 'Hello!!!'
        }
    
        render() {
            return (
                <div className="app" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h1>App</h1>
                    <p>value : {this.state.value}</p>
                    <ComponentA value={this.state.value} setValue={this.setValue.bind(this)}/>
                    <ComponentB value={this.state.value} setValue={this.setValue.bind(this)}/>
                </div>
            )
        }
    
        // 设置数据
        setValue(value: string) {
            this.setState({value});
        }
    }
    
    export default App
    
  • 子组件A

    import React, {BaseSyntheticEvent} from "react";
    
    class ComponentA extends React.Component<any, any> {
    
        render() {
            console.log('ComponentA', this.props.value);
            return (
                <div className="component-a" style={{border: '1px solid #FF00FF', padding: '20px', margin: '20px'}}>
                    <h2>Component A : {this.props.value}</h2>
                    <input type="text" value={this.props.value} onChange={this.handlerChange.bind(this)}/>
                </div>
            )
        }
    
        handlerChange(e: BaseSyntheticEvent) {
            this.props.setValue(e.target.value);
        }
    }
    
    export default ComponentA;
    
  • 子组件B

    import React, {BaseSyntheticEvent} from "react";
    
    class ComponentB extends React.Component<any, any> {
    
        render() {
            console.log('ComponentB', this.props.value);
            return (
                <div className="component-b" style={{border: '1px solid #FF00FF', padding: '20px', margin: '20px'}}>
                    <h2>Component B : {this.props.value}</h2>
                    <input type="text" value={this.props.value} onChange={this.handlerChange.bind(this)}/>
                </div>
            )
        }
    
        handlerChange(e: BaseSyntheticEvent) {
            this.props.setValue(e.target.value);
        }
    }
    
    export default ComponentB;
    

跨组件通信(爷孙组件传递)

原理:改变父组件的值或调用父组件中的方法去影响子组件(当子组件中数据修改时调用父组件的方法来改变父组件中对应的值进而子组件中的值也随之改变)

在这里插入图片描述

方式一
  • 事件总线(bus.ts)

    import {createContext} from "react";
    
    interface DefaultValue {
        value: string,
        func: Function
    }
    
    const defaultValue: DefaultValue = {
        value: '',
        func: () => {
        }
    }
    
    // defaultValue 为默认传过去的值
    export const {Provider, Consumer} = createContext(defaultValue);
    
  • 父组件

    import React, {BaseSyntheticEvent} from "react";
    import ComponentA from "./components/ComponentA/ComponentA";
    import {Provider} from "./bus";
    
    class App extends React.Component<any, any> {
    
        state = {
            value: 'Hello'
        }
    
        render() {
            return (
                <div className="app" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h1>App</h1>
                    <input type="text" value={this.state.value} onChange={this.handlerChange.bind(this)}/>
                    <Provider value={{value: this.state.value, func: this.handlerChange.bind(this)}}>
                        <ComponentA/>
                    </Provider>
                </div>
            )
        }
    
        handlerChange(e: BaseSyntheticEvent) {
            this.setState({value: e.target.value});
        }
    
    }
    
    export default App
    
  • 中间组件

    import React from "react";
    import ComponentB from "../ComponentB/ComponentB";
    
    class ComponentA extends React.Component<any, any> {
        render() {
            return (
                <div className="component-a" style={{border: '1px solid #FF00FF', padding: '20px', margin: '20px'}}>
                    <h3>Component A</h3>
                    <ComponentB/>
                </div>
            )
        }
    }
    
    export default ComponentA;
    
  • 子组件

    import React, {BaseSyntheticEvent} from "react";
    import {Consumer} from "../../bus";
    
    class ComponentB extends React.Component<any, any> {
    
        state = {
            value: ''
        }
    
        render() {
            return (
                <div className="component-b" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h3>Component B</h3>
                    <Consumer>
                        {(data) => this.getData.call(this, data)}
                    </Consumer>
                </div>
            )
        }
    
        // 获取传过来的参数
        getData(data: { value: string; func: Function; }) {
            this.state.value = data.value;
            // return (<input type="text" value={data.value} onChange={data.func.bind(this)}/>);
            return (<input type="text" value={data.value} onChange={this.handlerChange.bind(this, data.func)}/>);
        }
    
        // 回调根组件方法 (原理还是从根组件修改影响子组件)
        handlerChange(func: Function, e: BaseSyntheticEvent) {
            func(e);
        }
    
    }
    
    export default ComponentB;
    
方式二
  • 事件总线(bus.ts)

    import {createContext} from "react";
    
    interface DefaultValue {
        value: string,
        func: Function
    }
    
    const defaultValue: DefaultValue = {
        value: '',
        func: () => {
        }
    }
    
    // defaultValue 为默认传过去的值
    export const Context = createContext(defaultValue);
    
  • 父组件

    import React, {BaseSyntheticEvent} from "react";
    import ComponentA from "./components/ComponentA/ComponentA";
    import {Context} from "./bus";
    
    class App extends React.Component<any, any> {
    
        state = {
            value: 'Hello'
        }
    
        render() {
            return (
                <div className="app" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h1>App</h1>
                    <input type="text" value={this.state.value} onChange={this.handlerChange.bind(this)}/>
                    <Context.Provider value={{value: this.state.value, func: this.handlerChange.bind(this)}}>
                        <ComponentA/>
                    </Context.Provider>
                </div>
            )
        }
    
        handlerChange(e: BaseSyntheticEvent) {
            this.setState({value: e.target.value});
        }
    
    }
    
    export default App
    
  • 中间组件

    import React from "react";
    import ComponentB from "../ComponentB/ComponentB";
    
    class ComponentA extends React.Component<any, any> {
        render() {
            return (
                <div className="component-a" style={{border: '1px solid #FF00FF', padding: '20px', margin: '20px'}}>
                    <h3>Component A</h3>
                    <ComponentB/>
                </div>
            )
        }
    }
    
    export default ComponentA;
    
  • 子组件

    import React from "react";
    import {Context} from "../../bus";
    
    class ComponentB extends React.Component<any, any> {
    
        static contextType = Context;
    
        state = {
            value: ''
        }
    
        render() {
            const ctx = this.context as { value: string, func: Function };
            console.log(ctx);
            return (
                <div className="component-b" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h3>Component B</h3>
                    <input type="text" value={ctx.value} onChange={ctx.func.bind(this)}/>
                </div>
            )
        }
    
    }
    
    export default ComponentB;
    

任意组件间通信

使用第三方框架 Redux Mobx Flux Dva 等等

children (插槽)

每个组件都可以获取到 props.children。它包含组件的开始标签和结束标签之间的内容

  • Layout组件 (Layout.tsx)

    import {Component} from "react";
    
    class Layout extends Component<any, any> {
        render() {
            console.log(this.props.children); // 数组
            return (
                <div className="layout">
                    {this.props.children}
                </div>
            );
        }
    }
    
    export default Layout;
    
  • Home组件 (About.tsx)

    import {Component} from "react";
    
    class About extends Component<any, any> {
        render() {
            return (
                <span className="home">
                    Hello About !!!
                </span>
            );
        }
    }
    
    export default About;
    
  • 用法 (App.tsx)

    import React from "react";
    import About from "./views/About/About";
    import Layout from "./views/Layout/Layout";
    
    class App extends React.Component<any, any> {
        render() {
            return (
                <div className="app" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                    <h1>App</h1>
                    <Layout>
                        Hello Layout !!!
                        <About/>
                    </Layout>
                </div>
            )
        }
    }
    
    export default App
    

生命周期

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
在这里插入图片描述

在这里插入图片描述

// App.tsx
import React, {BaseSyntheticEvent} from "react";
import Layout from "./views/Layout/Layout";

class App extends React.Component<any, any> {

    state = {
        isComponent: false,
        message: ''
    }

    render() {
        return (
            <div className="app" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>

                <h1>App</h1>

                <label htmlFor="is-component">
                    <input type="checkbox" id="is-component" checked={this.state.isComponent}
                           onChange={this.handlerChangeCheckbox.bind(this)}/>
                    <span>显示/隐藏 组件</span>
                </label>

                &nbsp;

                <input type="text" onChange={this.handlerChangeText.bind(this)}/>

                {this.state.isComponent || (<Layout message={this.state.message}>Hello Layout !!!</Layout>)}

            </div>
        )
    }

    handlerChangeCheckbox(e: BaseSyntheticEvent) {
        // 方式一
        this.setState({isComponent: e.target.checked});
        // 方式二
        this.state.isComponent = e.target.checked;
        this.forceUpdate();
    }

    handlerChangeText(e: BaseSyntheticEvent) {
        this.setState({message: e.target.value});
    }

}

export default App
// Layout.tsx
import {Component} from "react";

class Layout extends Component<any, any> {

    // 挂载时
    constructor(props: any) {
        super(props);
        console.log('挂载时 ----> constructor');
    }

    // 挂载时 (更新时-props、setState、forceUpdate)
    render() {
        console.log('挂载时/更新时 ----> render');
        return (
            <div className="layout" style={{border: '1px solid #00FFFF', padding: '20px', margin: '20px'}}>
                <p>{this.props.children}</p>
                <p>来自Input : {this.props.message}</p>
            </div>
        );
    }

    // 挂载时 调用接口位置
    componentDidMount() {
        console.log('挂载时 ----> componentDidMount');
    }

    // 更新时
    componentDidUpdate(prevProps: Readonly<any>, prevState: Readonly<any>, snapshot?: any) {
        console.log('更新时 ----> componentDidUpdate', prevProps, prevState, snapshot);
    }

    // 卸载时
    componentWillUnmount() {
        console.log('销毁时 ----> componentWillUnmount');
    }
}

export default Layout;

Ant Design of React

$ npm i antd

在这里插入图片描述

App.tsx

// 图标
import {
    MenuFoldOutlined,
    MenuUnfoldOutlined,
    UploadOutlined,
    UserOutlined,
    VideoCameraOutlined,
} from '@ant-design/icons';

// 组件
import {Layout, Menu} from 'antd';

// React核心
import React, {Component} from 'react';

// Home组件
import About from "./views/About/About";

// 当前页面样式
import './App.css';

// Layout组件内的组件
const {Header, Sider, Content} = Layout;

class App extends Component<any, any> {

    state = {
        collapsed: false,
        defaultSelectedKeys: ['1'],
        menuList: [
            {key: '1', icon: <UserOutlined/>, label: '菜单 1'},
            {key: '2', icon: <VideoCameraOutlined/>, label: '菜单 2'},
            {key: '3', icon: <UploadOutlined/>, label: '菜单 3'},
        ]
    }

    render() {
        return (
            <>
                {/*整体布局*/}
                <Layout className="layout">

                    {/*侧边栏*/}
                    <Sider trigger={null} collapsible collapsed={this.state.collapsed}>

                        {/*logo*/}
                        <div className="logo">
                            <img src="/favicon.svg" alt=""/>
                        </div>

                        {/*菜单*/}
                        <Menu theme="dark" mode="inline" defaultSelectedKeys={this.state.defaultSelectedKeys}
                              items={this.state.menuList}
                        />
                    </Sider>

                    {/*Header Content*/}
                    <Layout>

                        {/*Header*/}
                        <Header className="header">
                            {
                                React.createElement(this.state.collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
                                    className: 'trigger',
                                    onClick: this.setCollapsed.bind(this, !this.state.collapsed),
                                })
                            }
                        </Header>

                        {/*Content*/}
                        <Content className="content">
                            <About/>
                        </Content>

                    </Layout>

                </Layout>
            </>
        )
    }

    setCollapsed(isCollapsed: boolean) {
        this.setState({collapsed: isCollapsed});
    }
}

export default App;

App.css

@import url('antd/dist/antd.css');

.layout {
    height: 100%;
}

.layout .logo {
    height: 64px;
    line-height: 64px;
    text-align: center;
}

.layout .header {
    padding: 0 10px;
    background-color: #fff;
}

.layout .content {
    background-color: #fff;
    color: #333333;
    margin: 10px;
    border-radius: 3px;
    padding: 10px;
}

About.tsx

import {Component} from "react";

class About extends Component<any, any> {
    render() {
        return (
            <div className='home'>
                Hello About !!!
            </div>
        );
    }
}

export default About;

Hook

Hook 是 React 16.8 的新增特性。

它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hook - useState

相当于类中的state

import {FC, useState} from "react";
import {Button} from "antd";

const About: FC = () => {

    // count 原始数据
    // setCount 生成一个新的数据更换掉原始数据
    const [count, setCount] = useState<number>(0);

    return (
        <div className='home'>
            <h2>Hello About !!!</h2>
            <p>{count}</p>
            <Button type="primary" onClick={() => setCount(count + 1)}>Add</Button>
        </div>
    );
}

export default About;

回调函数 (对要返回的数据进行函数加工后再返回)

如:用于将传过来的值进行一些修改

import {FC, useState} from "react";
import './About.css';
import {Button} from "antd";

const About: FC = () => {

    const [count, setCount] = useState<number>(() => {
        return Math.random();
    })

    return (
        <div className='home'>
            <Button type="primary" onClick={() => setCount(count + 1)}>{count}</Button>
        </div>
    );
}

export default About;

Hook - useEffect (类似vue中的watch)

可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合

在这里插入图片描述

import {FC, useState, useEffect} from "react";
import {Button} from "antd";

const About: FC = () => {

    const [x, setX] = useState<number>(0);
    const [y, setY] = useState<number>(0);

    // 实时监听
    useEffect(() => console.log(`【deps=undefined】 (x, y) ---> (${x}, ${y})`), undefined);

    // 有针对性监听 - 类似于vue的watch
    useEffect(() => console.log(`【deps=[]】 (x, y) ---> (${x}, ${y})`), []);         // 只执行一次,什么都不监听
    useEffect(() => console.log(`%c【deps=[x]】 x ---> ${x}`, "background: #ff0000;"), [x]);   // 只监听count1
    useEffect(() => console.log(`%c【deps=[y]】 y ---> ${y}`, "background: #ff00ff;"), [y]);   // 只监听y

    return (
        <div className='home'>
            <h2>Hello About !!!</h2>
            <p>(x, y) ---&gt; ({x}, {y})</p>
            <Button type="primary" onClick={() => setX(x + 1)}>Add X</Button>
            <Button type="primary" onClick={() => setY(y + 1)}>Add Y</Button>
        </div>
    );
}

export default About;

监听滚动

在这里插入图片描述

import {FC, useState, useEffect, createRef} from "react";
import './About.css';

const About: FC = () => {

    const scrollRef = createRef<HTMLDivElement>();

    const [y, setY] = useState(scrollRef.current?.scrollTop);

    useEffect(() => {

        scrollRef.current!.addEventListener('scroll', (e: Event) => {

            const {scrollTop} = e.target as HTMLDivElement;

            setY(scrollTop);

        }, false)

    }, [])

    return (
        <div className='home' ref={scrollRef}>
            <div style={{height: '1000px', lineHeight: '1000px', textAlign: 'center', fontSize: '300px'}}>{y}</div>
        </div>
    );
}

export default About;

清除 useEffect 副作用

// code ···
{
    this.state.isShow || <About/>
}
// code ···
import {FC, useEffect} from "react";
import './About.css';

const About: FC = () => {

    useEffect(() => {
        const timer = setInterval(() => {
            console.log('Lee');
        })
        // 清除定时器
        return () => {
            clearInterval(timer);
        }
    })

    return (
        <div className='home'>
            Hello About !!!
        </div>
    );
}

export default About;

Hook - useRef createRef

获取组件实例或者DOM元素

useRef一般用于函数组件,createRef一般用于类组件

都不能为函数组件添加ref
属性 (Warning: Function components cannot be given refs. Attempts to access this ref will fail.)

在这里插入图片描述

import {Component} from "react";

class ComponentA extends Component<any, any> {
    render() {
        return (
            <div>Hello ComponentA !!!</div>
        );
    }
}

export default ComponentA;
import {createRef, FC, useEffect, useRef} from "react";
import ComponentA from "../../components/ComponentA/ComponentA";

const About: FC = () => {

    const componentRef = useRef(null);
    const divRef = useRef(null);

    const componentRef1 = createRef<ComponentA>();
    const divRef1 = createRef<HTMLDivElement>();

    useEffect(() => {
        console.log(componentRef, componentRef.current);
        console.log(divRef, divRef.current);
        console.log(componentRef1, componentRef1.current);
        console.log(divRef1, divRef1.current);
    }, [])

    return (
        <div className='home'>
            <ComponentA ref={componentRef}/>
            <div ref={divRef}>Hello About !!!</div>
            <ComponentA ref={componentRef1}/>
            <div ref={divRef1}>Hello About !!!</div>
        </div>
    );
}

export default About;

Hook - useContext

  • bus.ts
    import {createContext} from "react";
    
    const Context = createContext({});
    
    export {Context};
    
  • About.tsx
    import {FC} from "react";
    import {Context} from "../../bus";
    import ComponentA from "../../components/ComponentA/ComponentA";
    
    const About: FC = () => {
        return (
            <div className='home'>
                <Context.Provider value={{message: 'Hello', data: {name: 'Lee'}}}>
                    <ComponentA/>
                </Context.Provider>
            </div>
        );
    }
    
    export default About;
    
  • ComponentA.tsx
    import {FC, useContext} from "react";
    import {Context} from "../../bus";
    
    const ComponentA: FC = () => {
        const data = useContext(Context);
        console.log(JSON.stringify(data)); // {"message":"Hello","data":{"name":"Lee"}}
        return (
            <div>Hello ComponentA !!!</div>
        )
    }
    
    export default ComponentA;
    

基础 Router

$ npm install react-router-dom@6

在这里插入图片描述

  • main.tsx

    // 全局路由组件 用于初始路由
    import {BrowserRouter, HashRouter} from "react-router-dom";
    
    ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<HashRouter><App/> </HashRouter>);
    
  • App.tsx

    import {Route, Routes, Link} from "react-router-dom";
    import About from "./views/About/About";
    import About from "./views/About/About";
    import Message from "./views/Message/Message";
    
    class App extends Component<any, any> {
    
        state = {
            menuList: [
              {key: '1', icon: <UserOutlined/>, label: <Link to="/">About</Link>},
              {key: '2', icon: <VideoCameraOutlined/>, label: <Link to="/about">About</Link>},
              {key: '3', icon: <UploadOutlined/>, label: <Link to="/message">Message</Link>}
            ]
        }
    
        render() {
            return (
                <>
                    {/*菜单*/}
                    <Menu theme="dark" mode="inline" items={this.state.menuList}/>
    
                    {/* 路由容器 */}
                    <Routes>
                        <Route path="/" element={<About/>}/>
                        <Route path="/about" element={<About/>}/>
                        <Route path="/message" element={<Message/>}/>
                    </Routes>
                </>
            )
        }
    }
    
    export default App;
    

路由懒加载

在这里插入图片描述

import {lazy, Suspense, FC} from "react";
import {Route, Routes} from "react-router-dom";

const Login = lazy(() => import('../views/Login/Login'));
const Layout = lazy(() => import('../components/Layout/Layout'));
const Home = lazy(() => import('../views/Home/Home'));
const About = lazy(() => import('../views/About/About'));
const NotFound = lazy(() => import('../views/NotFound/NotFound'));

const Router: FC = () => {
    return (
        <Suspense fallback={<div>loading</div>}>
            <Routes>
                <Route path="/login" element={<Login/>}/>
                <Route path="/layout" element={<Layout/>}>
                    {/* index 默认二级路由*/}
                    <Route index element={<Home/>}/>
                    <Route path="about" element={<About/>}/>
                </Route>
                <Route path="*" element={<NotFound/>}/>
            </Routes>
        </Suspense>
    )
}

export default Router;

路由传参 useSearchParams useParams

在这里插入图片描述

// App.tsx

// React核心
import React, {FC} from 'react';
import {BrowserRouter, HashRouter, Route, Routes, useNavigate} from "react-router-dom";

// 组件
import About from "./views/About/About";
import About from "./views/About/About";
import Message from "./views/Message/Message";

// antd组件
import {Button} from "antd";

const App: FC = () => {

    const navigate = useNavigate();

    const go = (to: string) => {
        navigate(to, {replace: true});
    }

    return (
        <BrowserRouter>
            <Button type="primary" onClick={go.bind(null, '/')}>About</Button>
            <Button type="primary" onClick={go.bind(null, 'about?name=Lee&age=18&name=Tom')}>About</Button>
            <Button type="primary" onClick={go.bind(null, '/message/Lee/18/Tom')}>Message</Button>
            <hr/>
            <Routes>
                <Route path="/" element={<About/>}/>
                <Route path="/about" element={<About/>}/>
                <Route path="/message/:name/:age/:name" element={<Message/>}/>
            </Routes>
        </BrowserRouter>
    )
}

export default App;
// About.tsx
import React, {FC} from "react";

const About: FC = () => {
    return (
        <div className='home'>
            <h1>About</h1>
        </div>
    );
}

export default About;
// About.tsx
import {FC} from "react";
import {useSearchParams} from "react-router-dom";

const About: FC = () => {

    const [params] = useSearchParams();

    console.log(params);

    console.log(params.toString()); // name=Lee&age=18&name=Tom

    /**
     * name     Lee     URLSearchParams{}
     * age      18      URLSearchParams{}
     * name     Tom     URLSearchParams{}
     */
    params.forEach((value, key, parent) => console.log(key, value, parent));

    // 包含 key value
    const entries = params.entries();
    console.log(entries.next()); // {done: false, value: ['name', 'Lee']}
    console.log(entries.next()); // {done: false, value: ['age', '18']}
    console.log(entries.next()); // {done: false, value: ['name', 'Tom']}
    console.log(entries.next()); // {done: true, value: undefined}

    // 所有 key
    const keys = params.keys();
    console.log(keys.next()); // {done: false, value: 'name'}
    console.log(keys.next()); // {done: false, value: 'age'}
    console.log(keys.next()); // {done: false, value: 'name'}
    console.log(keys.next()); // {done: true, value: undefined}

    // 所有 value
    const values = params.values();
    console.log(values.next()); // {done: false, value: 'Lee'}
    console.log(values.next()); // {done: false, value: '18'}
    console.log(values.next()); // {done: false, value: 'Tom'}
    console.log(values.next()); // {done: true, value: undefined}

    const name = params.get('name');
    const names = params.getAll('name');

    return (
        <div className='about'>
            <h1>About</h1>
            <p>{name}</p>
            <p>{JSON.stringify(names)}</p>
        </div>
    );
}

export default About;
// Message
import {FC} from "react";
import {useParams} from "react-router-dom";

const Message: FC = () => {

    const params = useParams();

    console.log(params); // {name: 'Tom', age: '18'}

    return (
        <div className='message'>
            <h1>Message</h1>
            <p>{JSON.stringify(params)}</p>
        </div>
    );
}

export default Message;

嵌套路由 - Outlet

项目结构

在这里插入图片描述

src
  │  App.tsx
  │  index.css
  │  main.tsx
  │  vite-env.d.ts
  │  
  ├─components
  │  └─Layout
  │          Layout.tsx
  │          
  ├─router
  │      index.tsx
  │      
  └─views
      ├─About
      │  │  About.tsx
      │  │  
      │  └─Message
      │          Message.tsx
      │          
      ├─Home
      │      Home.tsx
      │      
      ├─Login
      │      Login.tsx
      │      
      └─NotFound
              NotFound.tsx

源码

// main.tsx

// 核心依赖
import React from 'react';
// 渲染依赖
import ReactDOM from 'react-dom/client';
// 入口组件
import App from './App';
// 全局样式
import './index.css';
// 全局路由组件
import {BrowserRouter} from "react-router-dom";

/**
 * 渲染根组件
 *      React.StrictMode 需要去掉严格模式
 *      原因:严格模式影响 useEffect 的执行时机
 * ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
 *      <React.StrictMode>
 *          <App/>
 *      </React.StrictMode>
 * );
 */
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<BrowserRouter><App/></BrowserRouter>);
// App.tsx

import React, {FC} from 'react';
import Router from "./router";

const App: FC = () => {
    return (<Router/>)
}

export default App;
// components/Layout/Layout.tsx

import React from 'react';

import {Layout as AntdLayout, Menu} from "antd";

import {Link, Outlet, useLocation} from "react-router-dom";

const {Sider, Header, Content} = AntdLayout;

// Layout
const Layout: React.FC = () => {

    const location = useLocation();

    let defaultSelectedKeys = [location.pathname];

    // 菜单数据
    const items = [
        {
            key: '/',
            label: <Link to="">菜单项 1</Link>
        },
        {
            key: '/about',
            label: <Link to="about">菜单项 2</Link>
        },
        {
            key: '3',
            label: '菜单项 3',
            children: [
                {
                    key: '3-1',
                    label: <Link to="404">菜单项 3-1</Link>
                }
            ]
        }
    ];

    return (
        <>
            <AntdLayout style={{height: '100%'}}>
                <Sider>
                    <Menu theme="dark" mode="inline" defaultSelectedKeys={defaultSelectedKeys} items={items}/>
                </Sider>
                <AntdLayout>
                    <Header>
                        <Link to="/login">登出</Link>
                    </Header>
                    <Content style={{
                        margin: '10px',
                        backgroundColor: '#ffffff',
                        padding: '10px',
                        borderRadius: '3px'
                    }}>
                        <Outlet/>
                    </Content>
                </AntdLayout>
            </AntdLayout>
        </>
    )
}

export default Layout;
// router/index.tsx

import {FC} from "react";
import {Route, Routes} from "react-router-dom";
import Login from "../views/Login/Login";
import Layout from "../components/Layout/Layout";
import Home from "../views/Home/Home";
import About from "../views/About/About";
import NotFound from "../views/NotFound/NotFound";

const Router: FC = () => {
    return (
        <Routes>
            <Route path="/login" element={<Login/>}/>
            <Route path="/" element={<Layout/>}>
                {/* index 默认二级路由*/}
                <Route index element={<Home/>}/>
                <Route path="about" element={<About/>}/>
            </Route>
            <Route path="*" element={<NotFound/>}/>
        </Routes>
    )
}

export default Router;
// views/NotFound/NotFound.tsx

import React from 'react';
import {Typography} from 'antd';

// 404
const NotFound: React.FC = () => {
    return (<Typography.Title>404页面</Typography.Title>)
}

export default NotFound;
// views/Login/Login.tsx

import React from 'react';
import {Button} from "antd";
import {useNavigate} from "react-router-dom";

// 登录
const Login: React.FC = () => {

    const navigate = useNavigate();

    // 登录
    const login = () => {
        navigate('/', {replace: true});
    }

    return (
        <>
            <h1>登录页面</h1>
            <Button type="primary" onClick={login}>登入</Button>
        </>
    )
}

export default Login;
// views/Home/Home.tsx

import React from 'react';
import {Card} from "antd";

// 首页
const Home: React.FC = () => {
    return (
        <Card type="inner" title="Home页面">
            <p>Home页面</p>
        </Card>
    )
}

export default Home;
// views/About/About.tsx

import React from 'react';
import Message from "./Message/Message";
import {Card} from "antd";

// 关于页面
const About: React.FC = () => {
    return (
        <Card type="inner" title="About页面">
            <p>About页面</p>
            <Message/>
        </Card>
    )
}

export default About;
// views/About/Message/Message.tsx

import React from 'react';
import {Card} from "antd";

// Message页面
const Message: React.FC = () => {
    return (
        <Card type="inner" title="Message页面">
            <p>Message页面</p>
        </Card>
    )
}

export default Message;

mobx

$ npm install mobx mobx-react-lite

在这里插入图片描述

// store/index.ts

import {makeAutoObservable} from "mobx";

class Store {

    public count: number = 0;
    public addCount = () => {
        this.count++;
    }

    // 计算属性
    get color() {
        return this.count % 2 === 0 ? 'red' : 'green';
    }

    constructor() {
        makeAutoObservable(this);
    }
}

const store = new Store();

export default store;
// views/Home/Home.tsx

import React from 'react';
import {observer} from "mobx-react-lite";
import {Button, Card} from "antd";
import store from "../../store";

// 首页
const Home: React.FC = () => {

    console.log(store);

    return (
        <Card type="inner" title="HOME">
            <p style={{color: store.color}}>Home 页面</p>
            <Button onClick={store.addCount}>{store.count}</Button>
        </Card>
    )
}

export default observer(Home);
// views/About/About.tsx

import React from 'react';
import {observer} from "mobx-react-lite";
import {Button, Card} from "antd";
import store from "../../store";

// 关于页面
const About: React.FC = () => {

    console.log(store);

    return (
        <Card type="inner" title="ABOUT">
            <p style={{color: store.color}}>About 页面</p>
            <Button onClick={store.addCount}>{store.count}</Button>
        </Card>
    )
}

export default observer(About);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prosper Lee

您的赏赐将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值