react
用于构建用户界面的js库。
印记中文
中文文档,比如:react、react-router、redux、react-redux等
特点
组件化、声明式、使用虚拟DOM+diff算法减少与真实DOM的交互。
babel
es6转es5、jsx转js、默认开启了严格模式(use strict)
虚拟DOM
本质是object类型的对象、虚拟DOM比较轻(属性比较少),最终会被转为真实dom
渲染页面
ReactDOM.render(<Component />, document.getElementById('root'))
react解析组件标签,找到对应的组件,调用该组件,将返回的虚拟dom转为真实dom,呈现页面
jsx
全称JavaScript xml,本质是React.createElement(component, props, children)方法的语法糖。
使用规则:
- 不要写引号
- 在标签中混入js表达式要用{}
- 类名写className(因为es6中类定义的关键字是class,为了避免所以使用className)
- 内联样式style={{key: value}}
- 只能有一个根标签
- 标签必须闭合
- 标签名小写,则转html里的同名标签,没有则报错
- 标签名大写,react渲染对象的组件,没有则报错
- xml早期用于存储和传输数据
- 注释 {/* content */}
jsx是 React.createElement() 的语法糖
{{}}
外层括号表示要写js表达式,内层括号表示要写对象
组件
- 类式组件(适用于复杂组件(有State))
- 函数式组件(适用于简单组件(无state))当然现在可以使用hook函数了useState()
state
state是一个对象
类式组件:修改state,使用setState,修改后的state会和原state合并
state = {};
对象式:this.setState({}, [callback])
函数式:
// 函数式会接收到两个参数,state、props
this.setState((state, props) => {}, [callback])
函数式组件:使用useState
let [lxh, setLxh] = useState({name: "lxh", age: 24});
修改state:修改后的state会覆盖原state
第一种写法:setLxh({...lxh, age: 25});
第二种写法:setLxh((value) => newValue);
参数:第一次初始化的值在内部做缓存
props
props是一个只读对象
类式组件:vscode快捷键(rcep)
第一种:(常用)
// 限制类型
static propTypes = {
name: PropTypes.string.isRequired,
fun: PropTypes.func,
boo: PropTypes.bool,
}
// 设置默认值
static defaultProps = {
name: "lxh",
}
第二种:
组件名.propTypes = {}
组件名.defaultProps = {}
函数式组件:vscode快捷键(rafcp)
组件名.propTypes = {}
组件名.defaultProps = {}
constructor构造器
可写可不写
写了
- 接收props,并且传给super(props),这样在构造器里就能拿到props
- 不接收props,在构造器里props就是undefined
ref
请勿过度使用ref
类式组件
第一种:字符串(不推荐,过时,效率不高),标签<input ref='xxx' />,通过this.refs.xxx获取
第二种:回调函数形式
如果是内联函数,在更新过程中会执行两次:
第一次传的值是null
第二次才传的DOM节点
使用标签<input ref={c => this.名称 = c} />
获取this.名称
这种情况无关要紧,可以写内联函数
如果需要避免执行两次:
不写内联函数,写成class绑定函数的形式
<input ref={this.xxx} />
xxx = (e) => {
this.yyy = e
console.log(e)
}
第三种:React.createRef,专人专用,如果使用多次,需要定义多次
<input ref={this.xxx} />
this.xxx = React.createRef()
函数式组件
使用useRef()
const lxh = React.useRef()
lxh.current.value
context
常用于祖组件和后代组合间通信
使用:
// 创建context容器对象
const lxhContext = React.createContext()
// 渲染子组件
<lxhContext.Provider value={数据}>
子组件
</lxhContext.Provider>
// 后代组件读取数据
// 第一种方式:只适用于类式组件
static contextType = lxhContext // 声明接收context
this.context // 读取context中的value数据
// 第二种方式:函数组件与类式组件都可以
<lxhContext.Consumer>
{
value => {
// value就是context中的value数据
return `${value}`
}
}
</lxhContext.Consumer>
// 可以使用useContext获取数据
const ctx = useContext(lxhContext)
// 开发中一般不用context,一般用它来封装组件
受控组件、非受控组件
表单中收集数据
受控组件:通过input提供的onChange()事件
<input onChange={this.xxx('yyy')} />
xxx = (name) => {
return (e) => {
this.setState({[name]: e.target.value})
}
}
<input onChange={(e) => this.xxx('yyy', e)} />
xxx = (name, e) => {
this.setState({[name]: e.target.value})
}
非受控组件:现取现用,操作DOM(ref),获取数据
推荐使用受控组件
高阶函数、函数的柯里化、纯函数
高阶函数:函数的参数是函数,函数的返回值是函数。符合其一就是高阶函数。
例如:Promise()、setTimeout()、map()等
函数的柯里化:
this.lxh(1)(2)
lxh = (a) => {
return (b) => {
// a = 1、b = 2
}
}
纯函数:只要是同样的输入(实参),必定得到同样的输出(返回值)
生命周期
类式组件
旧
render() {
// 必须使用
}
componentDidMount() {
// 常用,一般用于开启定时器、发送网络请求、订阅消息
}
componentWillUnmount() {
// 常用:一般开闭定时器、取消订阅消息
}
componentWillReceiveProps(props) {
/**
* 第一次接收props时,不执行,props改变时,执行,即将移除此生命周期
* 使用:UNSAFE_componentWillReceiveProps() {}
**/
}
componentWillMount() {
// 即将移除此生命周期,使用:UNSAFE_componentWillMount() {}
}
componentWillUpdate() {
// 即将移除此生命周期,使用:UNSAFE_componentWillUpdate
}
shouldComponentUpdate() {
// 必须写返回值,不然报错
return true // 不写这个生命周期,默认返回true
}
this.forceUpdate() // 强制更新
卸载组件:
ReactDOM.unmountComponentAtNode(document.getElementById('xxx'))
触发componentWillUnmount(),但不能卸载createRoot()创建的组件
createRoot()创建的组件卸载方式:
const ROOT = createRoot(document.getElementById('xxx'))
(注意:后面加render函数这样写:ROOT.render())
export default ROOT
import root from '../index'
root.unmount()
新
废弃了3个,新增了2个
过期的生命周期:
UNSAFE_componentWillMount()
UNSAFE_componentWillReceiveProps()
UNSAFE_componentWillUpdate()
getDerivedStateFromProps(props, state) {
/**
* 不常用
* 若state的值在任何时候都取决于props,就可以使用
* 缺点:派生状态会导致代码冗余,并使组件难以维护
* 使用时,前面加static
**/
return props // 必须返回一个state对象 或者 null,是对象与state合并
}
getSnapshotBeforeUpdate() {
/**
* 不常用
* 组件能在发生更改之前从DOM中捕获一些信息(如滚动位置),它的返回值传递给componentDidUpdate()
* 必须和componentDidUpdate一起使用,不然报错
* 必须返回一个值或者null,不能返回undefined
**/
return "snapshotValue" // 传给componentDidUpdate的第三个参数
}
componentDidUpdate(preProps, preState, snapshotValue) {
// 更新完成
}
例子:
import React, { Component } from "react";
class Home extends Component {
state = { arr: [] };
componentDidMount() {
let timer = setInterval(() => {
this.setState(
(state) => ({
arr: ["新闻" + (state.arr.length + 1), ...state.arr],
}),
() => {
if (this.state.arr.length >= 20) {
clearInterval(timer);
timer = null;
}
}
);
}, 1000);
}
componentWillUnmount() {}
getSnapshotBeforeUpdate() {
return this.refs.refStr.scrollHeight;
}
componentDidUpdate(preProps, preState, snapshot) {
this.refs.refStr.scrollTop += this.refs.refStr.scrollHeight - snapshot;
}
render() {
return (
<div
style={{
height: 100,
width: 200,
backgroundColor: "pink",
overflow: "auto",
}}
ref="refStr"
>
{this.state.arr.map((item, index) => {
return (
<div style={{ height: 20 }} key={index}>
{item}
</div>
);
})}
</div>
);
}
}
export default Home;
函数式组件
使用useEffect,可以在函数式组件中执行副作用操作(用于模拟类式组件里的生命周期钩子)
主要用于:
发送ajax请求
设置订阅、启动定时器
手动更改真实DOM
用法:
useEffect(() => {
// 在此可是执行任意带副作用操作
return () => {
// 在组件卸载前执行,比如清除定时器、取消订阅
}
}, []) // 如果指定的是[],回调函数只会在render()后执行
// 没有写[],就是监测所有state状态
/**
* 可以把useEffect看成是三个生命周期的组合
* componentDidMount
* componentDidUpdate
* componentWillUnmount
**/
路由
SPA:单页面应用,整个应用只有一个完整的页面,点击页面的链接,不会刷新页面,只会做页面的局部更新。
路由:一个路由就是一个映射关系(key:value)
key:路径、value:component或function
前端路由:浏览器端路由,value是component,用于展示页面内容
注册路由:<Route to='' component='' /> v5的方式
<Route to='' element='' /> v6的方式
工作过程:当浏览器的path等于对应的路由时,当前的路由就会变成对应的组件。
后端路由:value是function,用于处理客户端提交的请求
注册路由:router.get(path, (req, res) => {})
工作过程:当node接收到一个请求时,根据请求的路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据。
react-router-dom
安装:npm i -S react-router-dom@5 // v5的版本,现在默认安装的是v6
npm i -S react-router-dom // v6版本
浏览器:强制刷新,按住shift,点刷新
v5
// NavLink用于高亮,它的属性activeClassName用于选中的样式
<NavLink activeClassName='' className='' to='' replace></NavLink>
// Link没有activeClassName属性,默认是push模式
<Link className='' to='' replacee></Link>
// children,通过props来获取,下面两个相同
<NavLink>content</NavLink>
<NavLink children='content'></NavLink>
// Switch用于匹配到适合的第一个path时,就不往下匹配了
<Switch></Switch>
// 解决样式丢失问题
<link href='./css/bootstrap.css' />
第一种解决:<link href='/css/bootstrap.css' /> // 不用用./css...
第二种解决:<link href='%PUBLIC_URL%/css/bootstrap.css'>
第三种解决:用hash模式
// 严格匹配exact,不要随便开启,有时候开启不能匹配到二级路由
<Route path='' component='' exact></Route>
// Redirect重定向,一般写在路由的最下方
<Redirect to=''></Redirect>
路由传参:
// params方式:刷新不会丢失
<Link to={`/home/${id}`}></Link>
<Route path='/home/:id' /> // 需要占位
// 获取参数通过props.match.params
// 编程式
this.props.history.push(`/home/${id}`)
this.props.history.replace(`/home/${id}`)
// search方式(相当于ajax的query参数)刷新不会丢失
<Link to={`/home/?id=${id}&title=${title}`}></Link>
// 编程式
this.props.history.push(`/home?id=${id}`)
this.props.history.replace(`/home?id=${id}`)
/**
* 不需要声明接收
* 获取参数通过props.location.search,比如拿到'?id=xxx&title=lxh'
* react默认安装了querystring
*/
import qs from 'querystring';
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1)) // 需要去掉前面的问号
// state方式:history模式刷新不会丢失,hash模式刷新会丢失
<Link to={{pathname: '/home', state: {id: 'xxx'}}}></Link>
// 获取参数通过props.location.state
// 编程式
this.props.history.push({path: '/home', state: {id: ''}})
this.props.history.replace({path: '/home', state: {id: ''}})
history里的其他方法
this.props.history.go(-1)
this.props.history.goBack() // 后退
this.props.history.goForward() // 前进
// 一般组件使用路由的跳转,使用withRouter()
src/router/index.js
import React, { lazy } from "react";
const Home = lazy(() => import("../pages/home"));
const Login = lazy(() => import("../pages/login"));
const Error = lazy(() => import("../pages/error"));
// 无权限
export const commonRouter = [
{
path: "/login",
component: Login,
exact: true,
meta: {
title: "登录",
}
},
{
path: "/404",
component: Error,
exact: true,
meta: {
title: "404",
}
},
];
// 有权限
export const authRouter = [
{
path: "/",
component: Home,
exact: true,
meta: {
title: "首页",
}
},
];
src/app.jsx
import React, { Suspense } from "react";
import { BrowserRouter, Switch, Route, Redirect } from "react-router-dom";
import { commonRouter, authRouter} from "./router";
import Layout from "./layout";
const App = () => {
return (
<BrowserRouter>
<Switch>
<Suspense fallback={<>loading...</>}>
// 无权限
{
commonRouter.map(item => {
return (<Route
key={item.path}
exact={item.exact}
render={(props) => {
document.title = item.meta.title;
return (<item.component {...props} />)
}}
></Route>)
})
}
// 有权限
{
authRouter.map(item => {
return (<Route
key={item.path}
exact={item.exact}
render={(props) => {
document.title = item.meta.title;
sessionStorage.getItem("token")
? <Redirect to="/login" />
: <Layout><item.component {...props} /><Layout>
}}
></Route>)
})
}
<Redirect to="/404" />
</Suspense>
</Switch>
</BrowserRouter>
);
};
v6
Switch移除了,Routes代替
Route必须被Routes包裹,不然报错
component被element代替,caseSensitive区分path的大小写
<Route path='' element='' caseSensitive>
Navigate重定向,可以写replace属性,Redirect移除了
<Navigate to='' replace></Navigate>
<Route path='' element={<Navigate to='' />} />
NavLink的高亮样式,yyy选中的样式,写法不一样了,子组件高亮,不希望父组件高亮,父组件加end属性
<NavLink className={({isActive}) => isActive ? 'xxx yyy' : 'xxx'}></NavLink>
const xxxClassName = ({isActive}) => isActive ? 'xxx yyy' : 'xxx';
<NavLink className={xxxClassName} end></NavLink>
useRoutes路由表
useRoutes([
{
path: '/home/:id',
element: <Home />,
},
{
path: '/about',
element: <About />,
children: [
{
path: 'message', // 不写斜杠/
element: < />,
},
]
},
{
path: '*',
element: <Navigate to='/home' />,
},
])
/**
* <NavLink to='message'></NavLink> // 跳转子路由的写法
* <NavLink to='./message'></NavLink> // 跳转子路由的写法
* <Route index element={<Components />} /> 嵌套路由index是默认路由
* Outlet指定路由组件呈现的位置
*/
路由传参的方式:3种
params方式
<Link to={`/home/${id}`}></Link>
// 需要声明Route或useRoutes里:id
// 获取参数使用hooks函数,useParams
import { useParams } from 'react-router-dom';
const { id } = useParams()
search方式:相当于(query)
<Link to={`/home?id={id}&title=${title}`}></Link>
// 不需要声明
// 获取参数useSearchParams
import { useSearchParams } from 'react-router-dom';
const [search, setSearch] = useSearchParams() // 返回一个数组
const id = search.get('id') // 通过get获取对应的参数值
setSearch('id=xxx&title=yyy') // 更新search的参数
state方式:
<Link to=`/home` state={{id: ''}}></Link>
// 不需要声明
// 获取参数useLocation
import { useLocation } from 'react-router-dom';
const { state: { id } } = useLocation(); // 解构赋值直接使用id
编程式路由导航
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate();
navigate('/home', {
replace: true,
state: {
id: 'xxx',
}
});
navigate(1) // 前进
navigate(-1) // 后退
用的不多的hooks函数
import { useMatch,
useInRouterContext,
useNavigationType,
useOutlet,
useResolvedPath,
} from 'react-router-dom';
useMatch('/home/:id') // 必须传入路径
useInRouterContext() // 返回Boolean值,查看是否是Route包裹的组件
useNavigationType() // 返回PUSH、REPLACE、POP,查看当前的导航类型,刷新后就变成的POP
useOutlet() // 查看当前组件的嵌套路由,返回null说明还没有挂载
useResolvedPath('/user/#/asd?id=xxx&title=yyy') // 传入一个url值,返回path、hash、search值
src/router/index.js
import React, { lazy } from "react";
import { useRoutes, Navigate, Outlet } from "react-router-dom";
const Home = lazy(() => import("../pages/home"));
const Login = lazy(() => import("../pages/login"));
const Error = lazy(() => import("../pages/error"));
export default () => {
return useRoutes([
{
path: "/login",
element: <Login />,
},
{
path: "/",
element: <Home><Outlet /></Home>, // Outlet可写组件里
children: [
{
path: "table",
element: <Table />,
},
],
},
{
path: "/404",
element: <Error />,
},
{
path: "*",
element: <Navigate to='/404' />,
},
]);
};
src/app.jsx
import React, { Suspense } from "react";
import { BrowserRouter as Router } from "react-router-dom";
import RouterAll from "./router";
const App = () => {
return (
<Router>
<Suspense fallback={<>loading...</>}>
<RouterAll></RouterAll>
</Suspense>
</Router>
);
};
export default App;
类式组件跳转
// tsx版
import React, { ComponentClass } from "react";
import {
NavigateFunction,
useLocation,
useNavigate,
useParams,
} from "react-router";
export interface IRouteProps<Params = any, State = any> {
location: State;
navigate: NavigateFunction;
params: Params;
}
export function withRouter<P extends IRouteProps>(Child: ComponentClass<P>) {
return (props: Omit<P, keyof IRouteProps>) => {
const location = useLocation();
const navigate = useNavigate();
const params = useParams();
return (
<Child
{...(props as P)}
navigate={navigate}
location={location}
params={params}
/>
);
};
}
// 在组件里使用
import { withRouter, IRouteProps } from "../classRouter.tsx";
class Tags extends Component<IRouteProps> {
render() {
return <>
<button onClick={() => {
this.props.navigate('/home')
}}>跳转</button>
</>
}
}
export default withRouter(Tags)
Fragment
import React, { Fragment } from "react";
// 循环遍历使用Fragment,它只有一个属性key
<Fragment key={1}></Fragment>
<></> // 不能接收任何属性,一般用在根标签
PureComponent、memo
PureComponent
重写了:
shouldComponentUpdate(nextProps, nextState) {
return 布尔值
}
/**
* 只有props和state数据改变了,才会返回true
* 注意:只是进行了props和state数据的浅比较,如果只是数据对象内部数据改变了,返回false
* 不要直接修改state数据,而是要产生新数据
* 比如:state = {name: "lxh", arr: [1,2,3]}
* 修改1:let obj = this.state
* obj.name = "LXH"
* this.setState(obj) // 这样会返回false
* 修改2:let {arr} = this.state
* arr.push(4)
* this.setState({arr}) // 返回false
* this.setState({arr: [...arr, 4]}) // 返回ture
* // 不直接修改,产生新数据
**/
memo
主要用于函数组件
import React, { memo } from "react";
const Child = () ={
return (<div>child</div>)
}
export default memo(Child)
useCallback、usememo
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
useRef
Hook API 索引
children props、render props
children props
<A>
<B></B>
</A>
{this.props.children}
<div children='xxx'></div>
<div>xxx</div>
问题:如果B组件需要A组件的数据,做不到
render props
三个组件:
parent组件:<A render={(data) => <B data={data}></B>} />
A组件:{this.props.render(内部state数据data)}
B组件:读取A组件传过来的数据{this.props.data}
错误边境
错误边境:只捕获后代组件生命周期产生的错误。
当子组件出现错误的时候,触发getDerivedStateFromError调用,并携带错误信息
state = {hasError: ""}
static getDerivedStateFromError(error) {
// error是错误信息
return {hasError: error}
}
render() {
return (
<>
{this.hasError ? <>当前网络不稳定,请稍后再试</> : <Child />}
</>
)
}
注意:错误边境只适用于生产环境
componentDidCatch() {
// 渲染组件时出错,执行
// 主要用于统计页面的错误,发送请求给后台,反馈给服务器,通知编码人员进行bug的解决
}
使用方式:getDerivedStateFromError 配合 componentDidCatch
react+typescript写法
安装@types/下,如:npm i @types/react-router-dom
import React, { Component, createRef } from "react";
interface propsType {
id?: string;
[propsName: string]: any;
}
interface stateType {
name: string;
}
// 第一个props属性约定类型,第二个state状态约定类型,不写占位可以使用any<any,any>
class Tags extends Component<propsType, stateType> {
state = {
name: "liaoxianhui",
};
refName = createRef<HTMLInputElement>();
render() {
return (
<div>
<input type="text" ref={this.refName} />
<button
onClick={() => {
// as 断言 , ? 或者 ! 都可以三种方式
console.log((this.refName.current as HTMLInputElement).value);
}}
>
获取输入框的值
</button>
</div>
);
}
}
export default Tags;
import React, { FC, useRef, useState } from "react";
// 方法1:约定props里的属性类型
interface propsTypeOne {
id?: string;
}
// 方法2
interface propsTypeTwo {
id?: string;
name?: () => void;
[propName: string]: any;
}
// const Form = (props: propsTypeOne) => { // 方法1
const Form: FC<propsTypeTwo> = (props) => {
// 方法2
const [name, setName] = useState<string>("lxh");
const refName = useRef<HTMLInputElement>(null);
return (
<div>
<input type="text" ref={refName} />
<button
onClick={() => {
// as 断言 , ? 或者 ! 都可以三种方式
console.log(refName.current?.value);
}}
>
获取输入框的值
</button>
</div>
);
};
export default Form;
消息订阅与发布机制
插件PubSubJs
安装:npm install pubsub-js
yarn add pubsub-js
引入:import PubSub from 'pubsub-js';
发布:PubSub.publish("xxx","data")
订阅:PubSub.subscribe("xxx",(msg, data)=>{ // msg消息名、date数据
console.log(date)
})
跨域
// http-proxy-middleware react下载好了
const { createProxyMiddleware } = require('http-proxy-middleware') // react18中使用
// const createProxyMiddleware = require('http-proxy-middleware') // react17中使用
module.exports = function (app) {
app.use(
createProxyMiddleware('/api1', { // 遇到/api1的请求,触发该代理
target: 'http://loaclhost:5000', // 后端请求的基础路径
changeOrigin: true, // 控制服务器收到的请求头中host字段的值
pathRewrite: { // 去除请求前缀
'^/api1': ''
}
})
)
app.use(
createProxyMiddleware('/api2', {
target: 'http://localhost:6000',
changeOrigin: true,
pathRewrite: {
'/api2': ''
}
})
)
}
serve
测试打包好的项目在静态服务器,打包好后会提示serve -s build
- 安装:npm i -g serve
- 使用:serve -s build
- 使用指定端口号:serve -s build -l 4000