1、首先来认识什么是JSX
1.1 JSX简介
- 如下这个有趣的标签既不是字符串(没有引号)也不是HTML,它被称为 JSX,是一个 JavaScript 的语法扩展,全称为 JavaScript XML;
- 在 JSX 语法中,你可以在大括号内放置任何有效的 JavaScript 表达式。例如,2 + 3,user.firstName 或 formatName(user) 都是有效的 JavaScript 表达式;
- JSX 里标签的 class 变成了 className;
- 假如一个标签里面没有内容,你可以使用 /> 来闭合标签,就像 XML 语法一样。
// 1.单纯的JSX语法
const element = <h1>Hello, world!</h1>;
// 2.处理JS逻辑的JSX
const element = <h1>Hello, {name}, {2 + 3}</h1>;
// 3.JSX调用函数的返回结果进行渲染
function formatName(name) {
return name;
}
const element = <h1>Hello, {formatName('xiaoming')}</h1>;
// 4.自闭和
const element = <img src={user.avatarUrl} />;
1.2 JSX的内部机制
Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
// React.createElement() 会预先执行一些检查,以帮助你编写无错代码,但实际上createElement创建了一个这样的对象。注意:这是简化过的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
属性展开:如果你已经有了一个 props 对象,你可以使用展开运算符 ... 来在 JSX 中传递整个 props 对象。以下两个组件是等价的:
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}
function App2() {
const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;
}
2、熟悉JSX中的React语法
2.1 遍历 map
<ul>
{
this.state.listArr.map((item, index) => { // 遍历
return <li key={index}>{item}</li>;
})
}
</ul>
2.2 点击事件this指向
只在class类组件里面才有this的使用,函数式组件里面是没有this的
点击事件的this的使用详见下面的“3.1.1、应用”
2.3 state的操作
class类编程的时候,不能使用hook,可以使用this。
constructor() {
super(props);
// 在构建函数里面初始化state
this.state = {
language: 'zh'
};
}
// 更新数据必需使用setState
this.setState({
language: 'en'
});
// 注意:State 的更新可能是异步的。出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。
// 因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
// 错误
this.setState({
counter: this.state.counter + this.props.increment,
});
// 正确
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
不要直接操作state里面的数据 拷贝一个副本
deleteItem(index){
const list = [...this.state.list] // 不要直接操作state里面的数据 拷贝一个副本
list.splice(index, 1)
this.setState({
list
})
}
<todoItem key={index} index={index} delete={this.deleteItem.bind(this)} content={item}></todoItem> // 父组件
函数式编程的时候,可以使用hook,不能使用this(因为函数组件本质是一个js函数,函数是没有this的)。
// 初始化定义username
const [username, setUsername] = useState(''); // 数组解构
// 更新username使用 setUsername
setUsername('xiaoming');
useState是内置的hook,如下只是解读一下实现原理
function useState(initialState) {
let state = initialState; // 初始化状态值
const setState = (newState) => {
state = newState; // 更新状态值
// 触发组件重新渲染
// ...
};
return [state, setState]; // 返回状态值和更新状态的函数
}
2.4 不使用redux下的父子组件传值(建议使用Redux)
父传子
- 父组件通过属性的形式向子组件传递参数
- 子组件通过props接受父组件传递过来的数据
- 单项数据流 子组件不可以直接改变父组件的数据
<todoItem content={item}></todoItem> // 父组件
<li>{this.props.content}</li> // 子组件
子传父 子组件
handleDeleteClick(){
const {delete, index} = this.props // es6解构赋值
this.props.delete(index)
}
<li onClick={this.handleDeleteClick.bind(this)}>{this.props.content}</li> // 子组件
2.5 组件里面编写css样式
// 第一个{}指的是JSX语法的表达式 第二个{}表示一个对象
<button style={ {background: 'red'; color: 'white'} }>add</button>
2.6 运算符
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
{unreadMessages.length > 0 && (
<h2>
You have {unreadMessages.length} unread messages.
</h2>)
}
{isLoggedIn ? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
3、上手组件应用
3.1、class类组件
3.1.1、应用
- 组件一创建自动加载constructor构造函数,初始化this.state
- 执行render函数,输出一个DOM结构
- 在执行componentWillUnmount生命周期函数
- this.setState({})来更新数据
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
inputVal: '',
};
this.inputChange = this.inputChange.bind(this); // 点击事件 确定this的指向
this.handleClick = this.handleClick.bind(this); // 点击事件 确定this的指向
}
componentWillUnmount() { }
// 按钮点击事件
handleClick() {
this.setState({
list: [...this.state.list, this.state.inputVal],
inputVal: '',
});
}
// input改变事件
inputChange(e) {
this.setState({
inputVal: e.target.value,
});
}
// 删除
deleteData(index) {
// 操作state里面的数据的时候 建议拷贝一个副本 不要直接操作state数据
const list = [...this.state.list];
list.splice(index, 1);
this.setState({
list,
});
}
// 渲染的数据在抽出一个小的组件
getItems() {
return (
// 遍历
this.state.list.map((item, index) => {
return (
<li key={index} onClick={this.deleteData.bind(this, index)}>
{item}
</li>
);
})
);
}
render() {
return (
// 使用 Fragment 标签可以避免创建额外的 DOM 元素,使代码更加简洁
<Fragment>
<div>
<input value={this.state.inputVal} onChange={this.inputChange} />
<button className="btn-color" onClick={this.handleClick}>
add
</button>
</div>
<ul>{this.getItems()}</ul>
</Fragment>
);
}
}
3.1.2、生命周期
- 创建时:创建时调用constructor的构造函数,更新render函数,更新DOM和refs,当这个component渲染到DOM节点之后,会调用componentDidMount方法
- 更新时:新的props传入和调用setState,重新调用render函数,更新DOM和refs,当这个component渲染到DOM节点之后,会调用componentDidUpdate方法
- 卸载时:调用componentWillUnmount
更加详细的生命周期解释,但是大部分钩子函数都是不常用的,记住上面三个钩子函数就可以
- 组件的挂载流程:constructor ==> getDerivedStateFromProps ==> render ==> componentDidMount
- setState更新流程:getDerivedStateFromProps ==> shouldComponentUpdate ==> render ==> getSnapshotBeforeUpdate ==> componentDidUpdate
- 组件的卸载流程:componentWillUnmount
getDerivedStateFromProps
该方法必须是静态方法,不允许访问组件的实例(this),它接收两个参数:props 和 state。因此,它应该是一个纯粹的函数,只根据传入的参数来计算和返回新的状态,不应该有任何副作用操作。如果需要进行副作用操作或异步操作,应该使用 componentDidUpdate 或其他适当的生命周期方法。
static getDerivedStateFromProps(nextProps, prevState) {
// 根据新的 props 和当前的 state 来计算新的 state
if (nextProps.value !== prevState.value) {
return { value: nextProps.value };
}
return null; // 返回 null 表示不需要更新状态
}
shouldComponentUpdate
shouldComponentUpdate 用于决定组件是否需要进行更新渲染。它在组件更新之前被调用,并且接收两个参数:nextProps 和 nextState,分别表示组件即将接收的新属性和新状态。
默认情况下,shouldComponentUpdate 返回 true,即每次组件接收新的属性或状态都会触发更新。但是,在某些情况下,我们可以通过自定义实现 shouldComponentUpdate 来优化组件的性能,避免不必要的更新操作。
shouldComponentUpdate(nextProps, nextState) {
// 根据需要的条件判断是否需要更新
if (this.props.someProp !== nextProps.someProp) {
return true; // 需要更新
} if (this.state.someState !== nextState.someState) {
return true; // 需要更新
}
return false; // 不需要更新
}
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate方法可以用于获取组件更新前的 DOM 快照或其他有用的信息。它的返回值将作为参数传递给 componentDidUpdate 方法。通常情况下,它用于处理组件更新前后需要进行比较或操作的场景,例如在组件更新前保存滚动位置等。
getSnapshotBeforeUpdate(prevProps, prevState) {
// 获取组件更新前的快照
if (prevProps.someProp !== this.props.someProp) {
// 根据需要进行比较或操作
const snapshot = this.myRef.current.scrollTop;
return snapshot;
}
return null;
}
3.2、函数式组件
3.2.1、应用
- useState初始化username
- useEffect钩子函数,当jwt更新时会执行useEffect
- 点击事件不需要bind(this)
import React, { useEffect, useState } from 'react';
import { Dropdown, Space, Button, Menu } from 'antd';
import { useSelector } from '../../redux/hooks'
import { useNavigate } from 'react-router-dom'
export const Header: React.FC = () => {
const [username, setUsername] = useState('');
const navigate = useNavigate(); // 路由
const jwt = useSelector(s => s.user.token); // RTK方式 jwt一种认证机制
useEffect(() => {
if (jwt) {
setUsername('xiaoming');
}
}, [jwt])
const menuClickHnadler = (e: any) => { };
const onLogout = () => { navigate("/"); }
return (
<>
<Dropdown.Button
overlay={
<Menu
onClick={menuClickHnadler}
items={[
...languageList.map(item => {
return { key: item.code, label: item.name };
}),
{ key: 'new', label: '添加新语言' },
]}
></Menu>
}
>
{language === 'zh' ? '中文' : 'English'}
</Dropdown.Button>
{jwt ? (
<Button.Group className="button-group">
<Button onClick={onLogout}>注销</Button>
</Button.Group>
) : (
<Button.Group className="button-group">
<Button onClick={() => navigate("/register")}>注册</Button>
</Button.Group>
)
}
</>
);
};
对比
3.2.2、useEffect
函数式组件没有生命周期,因为生命周期函数是 React.Component 类的方法实现的,函数式组件没有继承 React.Component,所以也就没有生命周期。但是useEffect有生命周期函数同样的能力。
useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
- useEffect第二个参数,如果是[], 表示componentDidMount, dom初始化完成调用;
- useEffect第二个参数,如果是[count], 可以理解state里面的count发生变化才会调用
- useEffect第二个参数,如果不加,则可以理解为react的所有的生命周期,很容易陷入死循环,所以要避免第二个参数为空的情况
- 一个函数式组件里面可以多次使用useEffect
useEffect(()=>{
console.log("DidMount")
},[])
useEffect(()=>{
console.log("DidUpdate")
},[count])
4、更进一步:路由系统
- react-router 整个路由系统的核心,他提供了最基本的路由功能
- react-router-dom 用于浏览器,专门处理Web App的路由
- react-router-native 用于React Native,专门用来处理手机app的路由
- react-router-redux 提供了一整套中间件, 用于处理redux的集成
- react-router-config 用来静态配置路由
路由跳转
// JSX跳转
<Link to={`/detail/${id}`}>
// use hook跳转
const navigate = useNavigate();
onClick={() => navigate('/register')}
路由配置
import React, { useEffect } from 'react';
import styles from './App.module.css';
// HashRouter (BrowserRouter / HashRouter as Router)
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
import { HomePage, SignInPage, RegisterPage, DetailPage, SearchPage, ShoppingCartPage, PlaceOrderPage } from './pages';
// 私有路由
const PrivateRoute = ({ component, isAuthenticated, ...rest }) => {
// `rest`是一个包含剩余未解构的属性的对象
const routeComponent = props => {
return isAuthenticated ? React.createElement(component, props) : <Redirect to={{ pathname: '/signIn' }} />;
};
return <Route render={routeComponent} {...rest} />;
};
function App() {
return (
<div className={styles.App}>
<BrowserRouter>
<Switch>
// 跟路由
<Route exact path="/" component={HomePage} />
// 普通路由
<Route path="/signIn" component={SignInPage} />
<Route path="/register" component={RegisterPage} />
// 路由携带参数
<Route path="/detail/:touristRouteId" component={DetailPage} />
<Route path="/search/:keywords?" component={SearchPage} />
// 私有路由
<PrivateRoute isAuthenticated={jwt !== null} path="/shoppingCart" component={ShoppingCartPage} />
<PrivateRoute isAuthenticated={jwt !== null} path="/placeOrder" component={PlaceOrderPage} />
// 404路由
<Route render={() => <h1>404 not found 页面去火星了 !</h1>} />
</Switch>
</BrowserRouter>
</div>
);
}
export default App;
常用的 hook
useHistory:用于访问浏览器的历史记录对象,可以用来进行页面跳转和导航
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
// 例子:点击按钮后跳转到指定页面
const handleClick = () => {
history.push('/my-page');
};
return (
<button onClick={handleClick}>跳转</button>
);
}
useLocation:用于获取当前页面的位置信息,包括路径名、搜索参数等。
import { useLocation } from 'react-router-dom';
function MyComponent() {
const location = useLocation();
// 例子:根据当前路径名显示不同内容
let content;
if (location.pathname === '/home') {
content = <h1>首页</h1>;
} else if (location.pathname === '/about') {
content = <h1>关于我们</h1>;
} else {
content = <h1>未知页面</h1>;
}
return (
<div>{content}</div>
);
}
useParams:用于获取路由参数。
import { useParams } from 'react-router-dom';
function MyComponent() {
const { id } = useParams();
// 例子:根据路由参数显示不同内容
return (
<div>当前用户ID:{id}</div>
);
}
5、进阶:hook
5.1、什么是React Hook
- React Hook是React 16.8带来的全新特性,即将替换class的写法,使用函数式组件代替class组件的一种写法
- 没有破坏性改动、完全可选、百分百向后兼容、没有计划从React移除class
- React组件一直是函数,使用Hook完全拥抱函数
- 使用Hook规则: 只在最顶层使用Hook只在React函数中调用Hook
5.2 常见的hook
- useState(第一个hook)
- useEffect(第二个hook)
- 自定义hook(第三个hook):将组件逻辑提取到可重用的函数中,必须用use开头
import React, { useState, useEffect } from 'react';
// 必须用use开头
const useMouseMove = () => {
const [positions, setPositions] = useState({ x: 0, y: 0 });
useEffect(() => {
console.log('add effect', positions.x);
const updateMouse = (e: MouseEvent) => {
setPositions({ x: e.clientX, y: e.clientY });
};
document.addEventListener('click', updateMouse);
return () => {
console.log('remove effect', positions.x);
document.removeEventListener('click', updateMouse);
};
}, []);
return positions;
};
export default useMouseMove;
6、升华:Redux
6.1、Redux是什么
Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。
Redux使用不依赖任何框架,可以在React里面使用,也可以在Vue或这Angular里面使用
那么我们什么时候需要使用Redux呢?
- 第一,组件需要共享数据(或者叫做状态state)的时候;
- 第二,某个状态需要在任何地方都可以被随时访问的时候;
- 第三,某个组件需要改变另一个组件的状态的时候。
如下图简单概括一下:统一保存数据store,在隔离了数据与UI的同时,负责处理数据的绑定。
6.2、实战应用
npm install redux
// 创建数据存储store
import {createStore} from 'redux';
const defaultLanguage: IDefaultLanguage = {
value: 'zh',
languageList: [
{name: '中文', code: 'zh'},
{name: 'English', code: 'en'}
]
}
// reducer函数是一个纯函数,return直接返回结果,不经过其它的业务逻辑处理
const languageReducer = (state = defaultLanguage, action: IAction): IDefaultLanguage => {
switch (action.type) {
case 'change_language':
return {...state, value: action.payload}
case 'add_language':
return {...state, languageList: [...state.languageList, action.payload]}
default:
return state
}
}
const store = createStore(languageReducer);
// 引入自己创建的store仓库
import store from '../../redux/store';
// 获取store仓库里面的数据;
const [language, setLanguage] = useState(store.getState());
// store.dispatch派发数据
store.dispatch(addLanguageCreator(action));
// 改变语言的action
export const addLanguageCreator = (state: string): IAction => {
return {
type: CHANGE_LANGUAGE,
payload: state
}
}
// store.subscribe订阅数据
store.subscribe(() => {
setLanguage(store.getState());
});