项目构建
$ 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} ---> {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
组件通信
- 组件约定说明
- 组件名首字母必须大写
- 函数组件必须有返回值
- 组件名和函数名一致可进行标签自闭和
父传子
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>
<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 看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合
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) ---> ({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);