一、react基本概念
- 官方脚手架构建react应用
npm install cnpm -g
cnpm install create-react-app -g
create-react-app my-app
cd my-app
cnpm install
- 如何构建一个组件页面
(通过函数 或 class)
import React from 'react'
class XXX extends React.Component{
render(){
return <div>页面</div>
}
}
export default XXX;
- 状态管理state props
(状态变化会触发render,重新渲染页面)
constructor(props){
super(props)
this.state = {
name: this.props.name
}
}
- 组件间值传递
- 父组件 -> 子组件: 通过给子组件的设置属性
父:
子: this.props.name - 子组件 -> 父组件: 通过给子组件设置回调函数
父:
onChildOk = data => {
//接受子组件的值 }
子:this.props.onOk(data)
- 组件的生命周期
- construtor 初始化,构造函数
this.state = {} – 初始化state
super(props) – 在构造函数内部初始化this.props - UNSAFE_ componentWillMount 组件挂载前
给state赋值,请求数据、生成资源 - render 挂载 or 更新页面
初始化 或者 state、props发生变化时
渲染页面 (通过diff计算虚拟dom,生成/更新真实的dom) - componentDidMount 组件挂载后
请求数据、生成资源 - UNSAFE_ componentWillReceiveProps 仅在props发生变化时(组件初始化或state变化不触发)
UNSAFE_ componentWillReceiveProps(nextProps){
this.props.name xuyuan
nextProps xuzhif
} - shouldComponentUpdate 组件是否应该被更新
(nextProps,nextState)
返回true更新 返回false不更新 - UNSAFE_ componentWillUpdate 组件更新前
(nextProps,nextState)
尽量不要在这个阶段更新state,容易引起死循环 - componentDidUpdate 组件更新后
(prevProps,prevState) - componentWillUnmount 组件卸载前
释放资源
- react和传统前端开发的区别
(组件、状态和 JSX)
React 组件的模型是从 Model 到 View 的映射,这里的 Model 对应到 React 中就是 state 和 props。在过去,我们需要处理当 Model 变化时,DOM 节点应该如何变化的细节问题。而现在,我们只需要通过 JSX,根据 Model 的数据用声明的方式去描述 UI 的最终展现就可以了,因为 React 会帮助你处理所有 DOM 变化的细节。而且,当 Model 中的状态发生变化时,UI 会自动变化,即所谓的***数据绑定***。
所以我们可以把 UI 的展现看成一个***函数***的执行过程。其中,Model 是输入参数,函数的执行结果是 DOM 树,也就是 View。而 React 要保证的,就是每当 Model 发生变化时,函数会重新执行,并且生成新的 DOM 树,然后 React 再把新的 DOM 树以最优的方式更新到浏览器。
- 虚拟DOM
通过在内存中维护的virtual dom,状态发生变化时,通过diff算法计算出结果后再更新真实的dom - JSX语法糖 (js和html写在一起)
- 单页应用
- 组件化
以组件的方式构建UI
内置组件。内置组件其实就是映射到 HTML 节点的组件,例如 div、input、table 等等,作为一种约定,它们都是小写字母。
自定义组件。自定义组件其实就是自己创建的组件,使用时必须以大写字母开头,例如 MyButton、Modal - 单向数据流、状态管理
React 的核心机制是能够在数据发生变化的时候自动重新渲染 UI,那么势必要有一个让我们保存状态的地方,这个保存状态的机制就是 state。而 props 就是类似于 Html 标记上属性的概念,是为了在父子组件之间传递状态
在 React 之前,我们需要调用 DOM 的 API 来修改 DOM 树的结构,从而改变 UI 的展现。而在有了 React
之后,我们只需要在业务状态和 UI 状态之间建立一个绑定的关系就行了。绑定完成后,我们就不需要再关心怎么去精细控制 UI 的变化,因为
React 会在数据发生变化时,帮助我们完成 UI 的变化
- 相关资源
https://edu.51cto.com/course/12276.html?edu_recommend_adid=99
二、组件库
1. 用vite构建react应用
https://vitejs.cn/
为什么选择vite:新一代的构建工具,快速构建、打包和热重载
cnpm install vite -g
npm init vite@latest my-vite-app --template react
cd my-vite-app
cnpm install
2. 相关组件库
-
less
css预处理语言,扩展css语法
学习: https://less.bootcss.com/
安装: cnpm install less
cnpm install less-loader -
moment
高效的日期处理类库
学习: http://momentjs.cn/
安装: cnpm install moment --save -
Ant Design
企业级的UI组件库
学习: https://ant-design.antgroup.com/index-cn
安装: cnpm install antd --save
3. 学习react-router
路由的2种模式
○ hash http://www.baidu.cn/#/home
#以后的内容不会包含在http的请求中
○ history http://www.baidu.cn/home
需要后端处理匹配页面跳转
- react-router
前端路由
学习: https://zhuanlan.zhihu.com/p/431389907
安装: cnpm install react-router-dom
4. 学习全局状态管理(redux、mobx)
- mobx
全局状态管理
学习: https://www.mobxjs.com/
安装: cnpm install mobx
cnpm install mobx-react
<Provider store={XXX}>
</Provider>
@observable @action
@inject('XXX') @observer
开启装饰器:
cnpm install @babel/plugin-proposal-decorators
cnpm install @babel/plugin-proposal-class-properties
plugins: [
react({
babel: {
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
],
},
}),
],
5. 学习axios
- axios
http请求
学习: http://www.axios-js.com/
安装: cnpm install axios
封装请求
6. 构建完整的前端框架
- 框架库 react
- 前端路由 react-router、react-router-dom
- 全局状态管理 redux、mobx、vuex、flux
- UI库 antd、mui、bootstrap
- 请求库 axios 、fetch
- 第三方扩展 less、moment、lodash、charts、cookies、react-use等等
7. 封装前端功能,项目分层
项目结构示例
- my-app (项目文件夹)
- dist (打包目录)
- node_modules (nodejs模块目录) - src (项目主体内容)
- assets (静态资源、图片)
- model (全局状态管理)
- service (接口请求管理)
- utils (工具类)
- components (自定义组件)
- router (路由配置)
- config (配置文件、环境变量)
- views (页面组件)
-- login
-- home
-- query
-- system
- index.html (入口html)
- package.json (项目描述文件,项目基本信息、第三方依赖)
- vite.config.js (开发配置)
三、高阶组件 HOC
高阶组件能够让我们写出更易于维护的react代码 ,优雅地拆分组件和功能 (装饰者模式、 面向切面)
以组件为参数,返回值为新组件的函数。
用于组件重用和逻辑封装(如 修改、添加props、日志打点、权限控制、表单校验)
组件和 高阶组件的区别
- 组件是将 props 转换为 UI
- 高阶组件是将组件转换为另一个组件
function withUserData(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props)
this.state = { userInfo: {} }
}
componentDidMount() {
let userInfo = JSON.parse(localStorage.getItem("userInfo"))
this.setState({ userInfo })
}
render() {
// ...this.props 传递给当前组件的属性继续向下传递
return <WrappedComponent userInfo={this.state.userInfo} {...this.props} />
}
}
}
如何理解这段代码
inject(“store”)(observer(Home))
四、react hooks
本质是函数组件 + 状态 + 生命周期管理
代码更加简洁,业务逻辑复用,分离业务逻辑,让代码更加清晰和易于维护
- useState
让函数具有状态管理能力
const [count, setCount] = useState(0)
状态 count
更新count的函数 setCount
0 赋初始值
- useEffect
执行副作用,副作用是指一段和当前执行结果无关的代码。比如说要修改函数外部的某个变量,要发起一个请求,等等。也就是说,在函数组件的当次执行过程中,useEffect 中代码的执行是不影响渲染出来的 UI 的。
useEffect(callback, dependencies)
要执行的函数 callback
可选的依赖项数组 dependencies
useEffect 涵盖了 ComponentDidMount、componentDidUpdate 和 componentWillUnmount 三个生命周期方法
返回一个函数,用于在组件销毁的时候做一些清理的操作 (componentWillUnmount)
- 用法一 (没有传dependencies)
每次render后都会执行(ComponentDidMount、componentDidUpdate)
useEffect(() => {
console.log("执行代码")
return () => {
console.log("componentWillUnmount")
}
})
- 用法二 (dependencies传空数组)
只在首次执行的时候触发 (ComponentDidMount)
useEffect(() => {
console.log("执行代码")
return () => {
console.log("componentWillUnmount")
}
},[])
- 用法三 (dependencies传含参数的数组)
传入的参数变化才会执行
useEffect(() => {
console.log("执行代码")
return () => {
console.log("componentWillUnmount")
}
},[count,num])
总结一下,useEffect 让我们能够在下面四种时机去执行一个回调函数产生副作用:
1、每次 render 后执行:不提供第二个依赖项参数。比如useEffect(() => {})。
2、仅第一次 render 后执行:提供一个空数组作为依赖项。比如useEffect(() => {}, [])。
3、第一次以及依赖项发生变化后执行:提供依赖项数组。比如useEffect(() => {}, [deps])。
4、组件 unmount 后执行:返回一个回调函数。比如useEffect() => { return () => {} }, [])。
— 优化Hooks
- useCallback
缓存回调函数,避免重复计算
//使用前
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount(count + 1);
return <button onClick={handleIncrement}>+</button>
}
//使用后
function Counter() {
const [count, setCount] = useState(0);
const handleIncrement = useCallback(
() => setCount(count + 1),
[count], // 只有当 count 发生变化时,才会重新创建回调函数
);
return <button onClick={handleIncrement}>+</button>
}
- useMemo
缓存计算结果,避免重复计算
export default function App() {
const [count, setCount] = useState(0);
const [total, setTotal] = useState(0);
// 没有使用 useMemo,即使是更新 total, countToString 也会重新计算
const countToString = (() => {
console.log("countToString 被调用");
return count.toString();
})();
// 使用了 useMemo, 只有 total 改变,才会重新计算
const totalToStringByMemo = useMemo(() => {
console.log("totalToStringByMemo 被调用");
return total + "";
}, [total]);
return (
<div className="App">{countToString}</div>
)
}
- useRef
多次渲染之间共享数据 ,一般用useRef保存的数据,和UI的渲染无关,当ref更新时,不会触发组件的重新渲染
保存某个dom节点的引用
( 能否用 state 去保存 timer.current )
import React, { useState, useCallback, useRef } from "react";
export default function Timer() {
// 定义 time state 用于保存计时的累积时间
const [time, setTime] = useState(0);
// 定义 timer 这样一个容器用于在跨组件渲染之间保存一个变量
const timer = useRef(null);
// 开始计时的事件处理函数
const handleStart = useCallback(() => {
// 使用 current 属性设置 ref 的值
timer.current = window.setInterval(() => {
setTime((time) => time + 1);
}, 100);
}, []);
// 暂停计时的事件处理函数
const handlePause = useCallback(() => {
// 使用 clearInterval 来停止计时 window.clearInterval(timer.current);
timer.current = null;
}, []);
return <div>
{time / 10} seconds.
<br />
<button onClick={handleStart}>Start</button>
<button onClick={handlePause}>Pause</button>
</div>
}
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// current 属性指向了真实的 input 这个 DOM 节点,从而可以调用 focus 方法
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
- useContext
定义全局状态,在多个函数组件中共享状态
redux正是利用context的机制来提供更可靠的状态管理
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 创建一个 Theme 的 Context
const ThemeContext = React.createContext(themes.light);
function App() {
// 整个应用使用 ThemeContext.Provider 作为根组件
return (
// 使用 themes.dark 作为当前 Context
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
// 在 Toolbar 组件中使用一个会使用 Theme 的 Button
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
// 在 Theme Button 中使用 useContext 来获取当前的主题
function ThemedButton() {
const theme = useContext(ThemeContext);
return (
<button style={{
background: theme.background,
color: theme.foreground
}}>
I am styled by theme context!
</button>
);
}
- useReducer
useState 的替代方案,适用于更复杂的场景
import React,{useReducer} from 'react'
export default function ReducerDemo() {
const [count, dispath] = useReducer((state,action)=> {
if(action === 'add'){
return state + 1;
}
return state;
}, 0);
return (
<div>
<h1 className="title">{count}</h1>
<button className="btn is-primary"
onClick={()=> dispath('add')}
>Increment</button>
</div>
)
}
如何在hooks中使用mobx?
import React from "react";
import { inject, observer } from 'mobx-react';
function TestHook({ store }){
return <div>{store.User.state.tel}</div>
}
export default inject("store")(observer(TestHook));
五、React-Router v6
- 多级路由配置
//父级路由 path配置 * 表示模糊匹配
function App() {
return <BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/home/*" element={<Home />} />
<Route path="*" element={<Login />} />
</Routes>
</BrowserRouter>
}
export default App;
//子路由
function Home(){
return <Routes>
<Route path="page" element={<HomePage />} />
<Route path="art" element={<Art />} />
<Route path="upload" element={<UploadArt />} />
<Route path="*" element={<HomePage />} />
</Routes>
}
export default Home;
- 与v5的变化
- Routes 替代 Switch
- Route的element 替代 component
- Route废弃exact属性
- useRoutes代替react-router-config
import { useRoutes } from 'react-router-dom'
function App() {
return useRoutes([
{ path:'/login',element: <Login /> },
{ path:'/home/*',element: <Home /> },
{ path:'*',element: <Login /> },
])
}
export default App;
- useNavigate代替useHistory
//尽量改为用Hooks写页面
import { useNavigate } from 'react-router-dom';
const navigate = useNavigate()
navigate('/home')