React保姆级教学
一、创建第一个react项目
- 首先全局安装create-react-app
npm install -g create-react-app
- 安装好后,创建第一个react项目,注意项目名称要小写,并且不能有中文
npx create-react-app my-app
- npx Node.js工具命令,查找并执行后续的包命令
- create-react-app 核心包(固定写法),用于创建React项目
- my-app React项目的名称(可以自定义)
- 创建好后,cd到项目目录下面,执行npm start即可
cd my-app
npm start
二、JSX基本语法与react基础知识
1、 插值语法:
JSX可以通过大括号语法{},识别JS表达式,比如变量,函数调用,方法调用等
,但是只有表达式才可以。if else等语句是不可以的
// 项目的根组件
function func() {
return '调用了函数'
}
const count = 10
function App() {
return (
<div className="App">
{/* 使用引号传递字符串 */}
{'this is App'}<br />
{/* 使用js变量 */}
{count}<br />
{/* 调用函数 */}
{func()}<br />
{/* 方法的调用 */}
{new Date().getDate()}<br />
{/* 使用js对象 */}
<div style={{ color: 'red' }}>使用了js对象</div>
</div>
);
}
export default App;
2、 循环一个简单列表
使用的核心方法是map
// 项目的根组件
const list = [
{ id: '1001', name: 'Vue' },
{ id: '1002', name: 'React' },
{ id: '1003', name: 'Angular' },
]
function App() {
return (
<div className="App">
<ul>
{list.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
export default App;
3、 实现简单条件渲染
两种方法:使用逻辑与运算符 &&
或者使用三元运算符
// 项目的根组件
const isLogin = true
function App() {
return (
<div className="App">
{/* 逻辑与运算符 && */}
{isLogin && <span>已经登录了</span>}<br />
{/* 三元运算符 */}
{isLogin ? <span>已经登录了</span> : <span>用户未登录</span>}
</div>
);
}
export default App;
4、 实现复杂的条件渲染
通过使用自定义函数和if语句进行渲染
// 项目的根组件
const type = 1 // 0 1 3
function changeType() {
if (type === 0) {
return <div>我是第一种情况</div>
} else if (type === 1) {
return <div>我是第二种情况</div>
} else {
return <div>我是第三种情况</div>
}
}
function App() {
return (
<div>
{/* 通过调用函数实现条件渲染 */}
{changeType()}
</div>
);
}
export default App;
5、 事件绑定
// 项目的根组件
// 普通的点击事件
const handleClick1 = () => {
console.log('点击了button按钮')
}
// 获取点击事件的对象e
const handleClick2 = (e) => {
console.log('点击了button按钮', e)
}
// 获取自定义参数
const handleClick3 = (name) => {
console.log('点击了button按钮', name)
}
// 获取自定义参数,并且获取到点击事件的对象e
const handleClick4 = (name, e) => {
console.log('点击了button按钮', name, e)
}
function App() {
return (
<div>
{/* 获取自定义参数,一定要写成箭头函数的形式,否则是无法使用的 */}
<button onClick={handleClick1}>普通的点击事件</button>
<button onClick={handleClick2}>获取点击事件的对象e</button>
<button onClick={() => handleClick3('xiaoming')}>获取自定义参数</button>
<button onClick={(e) => handleClick4('xiaoming', e)}>获取自定义参数,并且获取到点击事件的对象e</button>
</div>
);
}
export default App;
6、 基础组件(函数组件)
首字母必须大写
// 项目的根组件
function Button() {
return <button>this is my componment</button>
}
function App() {
return (
<div>
{/* 使用组件 */}
<Button></Button>
</div>
);
}
export default App;
7、 使用useState
他是一个React 的Hook(函数)。它允许我们向组件添加一个状态变量
,从而控制影响组件的渲染结果
状态变量一旦发生变化,视图UI也会跟着变化(数据驱动视图)
调用useState后会返回一个数组,数组中的第一个参数是状态变量,第二个参数是set函数,用于修改状态变量。调用useState传入的参数作为状态变量的初始值
// 项目的根组件
import { useState } from 'react'
function App() {
// 1.调用useState添加一个状态变量
const [count, setCount] = useState(0)
// 2.点击事件回调
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<button onClick={handleClick}>{count}</button>
</div>
);
}
export default App;
使用useState的规则
useState的状态变量是不可以进行直接的修改的,直接修改视图无法进行更新。 需要调用他的set方法对数据进行替换。
8、 基础样式控制
使用行内样式,{}大括号内部必须是一个对象的形式,并且如果使用多个单词的样式,要使用驼峰命名
。例如fontSize。
使用类名的时候要注意,这里的属性名为className而不是class
// 项目的根组件
import './index.css'
function App() {
return (
<div>
{/* 行内样式控制 要使用{ 样式对象 } */}
<div style={{ color: 'red' }}>this is div</div>
{/* 使用类名 */}
<div className="fontColor">this is div</div>
</div>
);
}
export default App;
9、 动态类名
使用模板字符串,控制类名的隐藏和现实
<li className="nav-sort">
{/* 高亮类名: active */}
{tabs.map(item => (
<span
className={`nav-item ${type === item.type && 'active'}`}
key={item.type}
onClick={() => handleTabChange(item.type)}>
{item.text}
</span>
))}
</li>
还可以使用第三方classnames库
,使用这个库以后可以将这种繁琐的拼接字符串判断类名的方式,转换为对象的形式进行拼接
;增加代码可读性以及优化代码开发过程。
className={`nav-item ${type === item.type && 'active'}`}
// 转换后
className={classNames('nav-item', {active: type === item.type})}
10、 操作表单,实现表单受控绑定。
相当于数据的双向绑定,实现原理依旧是使用useState,在表单的onChange事件处调用useState的第二个参数的方法,改变
import { useState } from "react"
const App = () => {
const [value, setValue] = useState('')
return (
<div>
<input type="text" value={value} onChange={(e) => setValue(e.target.value)}></input>
</div>
)
}
export default App
11、 获取DOM
主要是使用useRef
import { useRef } from "react"
const App = () => {
const inputRef = useRef(null)
const showDom = () => {
console.log(inputRef)
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={() => showDom()}>获取dom</button>
</div>
)
}
export default App
12、 好用的第三方库,
- uuid,用于生成随机数。
- dayjs,用于快速格式化日期。dayjs中文文档
- Lodash,高性能JS工具库,它是一个一致性、模块化、高性能的 JavaScript 实用工具库。用法见官网Lodash中文文档
- Normallize.css 初始化样式,例如padding,margin等等
三、组件通信
1、 父传子
// 父传子
// 1、父组件传递数据,子组件标签身上绑定属性
// 2、子组件接收数据,props的参数
function Son(props) {
console.log(props)
return <div>{props.name}</div>
}
const App = () => {
const name = 'this is app name'
return (
<div>
<Son name={name}></Son>
</div>
)
}
export default App
子组件参数props的说明:
- props可以传递任何类型的数据,数字、字符串、布尔、数组、对象、函数、JSX都可以进行传递;
- props是只读的属性,既单向数据流,不允许直接修改props中的值,父组件的值只能由父组件进行修改;
(1)特殊的props:children,如果在子组件的标签包裹了其他标签,那么被包裹的内容就可以在子组件的props中多出一个children属性
;
function Son(props) {
console.log(props)
}
const App = () => {
const name = 'this is app name'
return (
<div>
<Son>
<span>this is span</span>
</Son>
</div>
)
}
export default App
2、 子传父
主要原理是应用props的特性,可以传递任何类型;父组件定义一个函数,用于接收子组件传递的参数。然后把函数传递给子组件,让子组件调用。
// 子传父
// 父组件定义一个函数,用于接收子组件传递的参数
// 然后把函数传递给子组件,让子组件调用
function Son({ onGetSonMsg }) {
const sonMsg = 'this is son msg'
return (
<div>
this is son
<button onClick={() => onGetSonMsg(sonMsg)}> send</button>
</div>
)
}
const App = () => {
const getMsg = (msg) => {
console.log(msg)
}
return (
<div>
<Son onGetSonMsg={getMsg} />
</div>
)
}
export default App
3、 借助“状态提升”
机制实现兄弟组件通信
什么是状态提升?既通过共同的父组件进行数据传递
;
import { useState } from 'react'
// 借助状态提升机制实现兄弟组件通信
function A({ onGetAMsg }) {
const AMsg = 'this is A msg'
return (
<div>
this is A
<button onClick={() => onGetAMsg(AMsg)}> send</button>
</div>
)
}
function B({ msg }) {
return (
<div>
this is B,{msg}
</div>
)
}
const App = () => {
const [AMsg, setAMsg] = useState('')
const getAMsg = (msg) => {
console.log(AMsg)
setAMsg(msg)
}
return (
<div>
<A onGetAMsg={getAMsg}></A>
<B msg={AMsg}></B>
</div>
)
}
export default App
4、 使用context机制跨层传递数据
主要原理是使用createContext
import { createContext, useContext } from 'react'
// 示例,将APP里面的数据传递到B组件
// 层级关系是 <App> <A> <B></B> </A> </App>
// 1、先使用createContext创建一个上下文对象
const MsgContext = createContext()
function A() {
return (
<div>
this is A
<B></B>
</div>
)
}
function B() {
// 3、在底层组件通过useContext钩子函数来使用数据
const msg = useContext(MsgContext)
return (
<div>
this is B,
{msg}
</div>
)
}
const App = () => {
const msg = 'this is app msg'
return (
<div>
{/* 2、在顶层组件通过provider组件,提供数据 */}
<MsgContext.Provider value={msg}>
this is App
<A/>
</MsgContext.Provider>
</div>
)
}
export default App
四、常用钩子函数介绍
- useEffect是一个React Hook函数,用于在React组件中创建
不是由事件引起而是由渲染本身引起的操作
,比如发送AJAX请求,更改DOM等等。useEffect(() =>{}, [])
- 参数1是一个函数,可以把它叫做
副作用函数
,在函数内部可以放置要执行的操作
- 参数2是一个数组(可选参),在数组里放置依赖项,不同依赖项会影响第一个参数函数的执行,
当是一个空数组的时候,副作用函数只会在组件渲染完毕之后执行一次
- useEffect的不同依赖项参数的不同执行表现
- 1、什么也不传的情况 页面初始化以及组件更新都会调用
- 2、传空数组 页面初始化会调用
- 3、传一个依赖项,只有依赖项变化的时候才会调用
import { useEffect, useState } from "react"
const App = () => {
const [count, setCount] = useState(0)
const [sum, setSum] = useState(0)
// 1、什么也不传的情况 页面初始化以及组件更新都会调用
// useEffect(() => {
// console.log('副作用函数执行了!!')
// })
// 2、传空数组 页面初始化会调用
// useEffect(() => {
// console.log('副作用函数执行了!!')
// }, [])
// 3、传一个依赖项,只有依赖项变化的时候才会调用
useEffect(() => {
console.log('副作用函数执行了!!')
}, [count])
return (
<div>
this is APP
<button onClick={() => setCount(count + 1)}> count+{count}</button>
<button onClick={() => setSum(sum + 1)}> sum+{sum}</button>
</div>
)
}
export default App
- 清除副作用,例如当我们在组件初始化的时候创建一个定时器,在组件销毁的时候要清除定时器,否则会一直占用资源。
语法是在useEffect内部return一个函数,函数内部执行清除操作
。清除副作用做常见的时机是在组件销毁的时候
import { useState, useEffect } from "react"
function Son() {
useEffect(() => {
const timer = setInterval(() => {
console.log('ding...')
}, 1000);
return () => {
console.log('组件卸载,清除副作用!')
clearInterval(timer)
}
}, [])
return <div>this is son</div>
}
const App = () => {
const [show, setShow] = useState(true)
return (
<div>
{/* 模拟组件销毁 */}
{show && <Son />}
<button onClick={() => setShow(false)}>卸载组件</button>
</div>
)
}
export default App
4、自定义Hook函数
自定义Hook是以 use
打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用
封装通用Hook函数的通用思路
- 生命一个以use开头的函数;
- 在函数体内封装可服用的逻辑;
- 要把组件中用到的状态或者回调return出去;
- 在那个组件重要用到这个逻辑,就在那里调用这个函数,结构赋值所用到的状态或者回调函数;
import { useState } from "react"
const useShowDiv = () => {
const [show, setShow] = useState(true)
const onChangeShow = () => {
setShow(!show)
}
return {
show,
onChangeShow
}
}
const App = () => {
const { show, onChangeShow } = useShowDiv()
// const [show, setShow] = useState(true)
// const onChangeShow = () => {
// setShow(!show)
// }
return (
<div>
{show && <div>this is div</div>}
<button onClick={() => onChangeShow()}>切换div状态</button>
</div>
)
}
export default App
5.React Hook函数的使用规则
- 只能在组件中或者其他自定义Hook函数中调用
- 只能在组件的顶层调用,
不能嵌套在if、for、其他函数中
以上两种情况,不符合条件时候开发工具和浏览器会报错,注意一下即可。
五、Redux快速上手
1. 环境搭建
在React中使用redux,官方要求安装俩个其他插件-ReduxToolkit和react-redux
- Redux Toolkit(RTK)-官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
- 简化store的配置方式
- 内置immer支持可变式状态修改
- 内置thunk更好的异步创建
- react-redux-用来 链接 Redux和 React组件 的中间件
安装插件
npm i @reduxjs/toolkit react-redux
2. store目录结构设计
3. 使用流程
ReduxToolkit和react-redux搭配使用流程
以一个异步请求为例
// 先引入createSlice方法
import { createSlice } from "@reduxjs/toolkit"
import axios from 'axios'
// 创建store
const foodsStore = createSlice({
// 名称
name: 'foods',
initialState: {
// 设置初始值为空数组
foodsList: []
},
reducers: {
// 声明同步操作函数 action
setFoodsList(state, action) {
state.foodsList = action.payload
}
}
})
// 获取创建的action
const { setFoodsList } = foodsStore.actions
// 创建异步方法,异步方法的语法必须return一个方法,方法的第一个参数为dispach。
// 可用于提交action。在异步方法里面调用网络请求,将请求后的得到的数据调用同步
// 方法赋值给store
const fetchFoodsList = () => {
return async (dispatch) => {
const res = await axios.get('http://localhost:3004/takeaway')
// 调用dispatch函数
dispatch(setFoodsList(res.data))
console.log(res)
}
}
// 导出异步方法让外界调用
export { fetchFoodsList }
const reducer = foodsStore.reducer
// 导出reducer
export default reducer
然后在store目录下的index.js文件中,收集modules下创建的store
import foodsReducer from './modules/takeaway'
import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({
reducer: {
foods: foodsReducer
}
})
export default store
首次使用需要在index.js里面注入store
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import { Provider } from 'react-redux'
import store from './store'
const root = createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>
)
使用store中的数据,需要在组件内调用useSelector方法
const {foodsList} = useSelector(state => state.foods)
六、ReactRouter快速上手
**说明: 一共有三个版本,
**
- web
- native
- any
我们使用的是专门用于前端开发的web版本
react-router-dom
1. 基本使用
- 创建router目录以及index.js配置文件,通过
createBrowserRouter
创建router,并导出router
import Article from '../page/Article'
import Login from '../page/Login'
import { createBrowserRouter } from 'react-router-dom'
const router = createBrowserRouter([
{
path: '/login',
Component: Login
},
{
path: '/article',
Component: Article
}
])
export default router
- 在index.js引入路由组件RouterProvider,以及创建好的router,把router注入到路由组件中
import { RouterProvider } from 'react-router-dom'
import router from './router'
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
2. 导航方式
- 声明式写法:以组件的形式进行跳转,react-router-dom提供了一个< Link /> 组件
<Link to="/login">跳转到登录</Link>
- 编程式写法:以代码的形式跳转
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
navigat('/login')
3. 路由传参
- searchParmas方式
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
// 直接用?在url后面,各个参数用&拼接
navigat('/login?id=1001&name=jack')
// 使用useSearchParams接收
import { useSearchParams } from 'react-router-dom'
// useSearchParams 返回一个数组
const [params] = useSearchParams()
const id = params.get('id')
const name = params.get('name')
- params方式,很像vue里面的动态路由,简直一模一样
import { useNavigate } from 'react-router-dom'
const navigate = useNavigate()
// 直接拼接在url后面,多个参数就一直往后面拼接
navigat('/login/1001/jack')
{
path: '/login/:id/:name',
Component: Login
}
import { useParams } from 'react-router-dom'
const params = useParams()
const id = params.id
const name = params.name
4. 嵌套路由
- 使用children配置嵌套路由
- 在需要嵌套路由的地方使用组件
<Outlet />
组件
{
path: '/article',
Component: Article,
children: [
{
path: '/component1',
element: <Component1/>
},
{
path: '/component2',
element: <Component2/>
},
]
},
5.默认二级路由
去掉二级路由中的path,换成index并设置为true就行了
{
path: '/article',
Component: Article,
children: [
{
index: true,
element: <Component1/>
},
{
path: '/component2',
element: <Component2/>
},
]
},
6.配置未找到路由的默认展示页面,既404页面
{
path: '*',
element: <NotFound/>
},
7.两种路由模式
以上的案例都是使用的history模式,需要后端支持。
- history模式创建的router是使用createBrowserRouter;
- hash模式创建的router是使用createHashRouter,url上会拼接一个#;
8.路由懒加载
import { lazy, Suspense } from 'react'
// 使用lazy函数对组件动态导入
const Home = lazy(() => import('@/pages/Home'))
const Article = lazy(() => import('@/pages/Article'))
const Publish = lazy(() => import('@/pages/Publish'))
const router = createBrowserRouter([
{
path: '/',
element: <AuthRoute><Layout /></AuthRoute>,
children: [
{
index: true,
element: <Suspense fallback={'加载中...'}> <Home /> </Suspense>
},
{
path: '/article',
element: <Suspense fallback={'加载中...'}> <Article /> </Suspense>
},
{
path: '/publish',
element: <Suspense fallback={'加载中...'}> <Publish /> </Suspense>
}
]
},
{
path: '/login',
element: <Login />
},
])
9.history模式可能遇到的问题
1、多级路由地址的时候,刷新页面,引入的css、js等文件引入失败的问题。
七、两个联想提示配置
- 路径中使用@匹配src,要使用craco插件,因为cra既create-react-app把配置包在黑盒里面,无法直接配置。所以需要使用craco插件进行配置;
npm i -d @craco/craco
下载插件后新建craco.config.js文件,进行如下配置
const path = require("path")
module.exports = {
webpack: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
- @符号路径的联想功能
新建jsconfig.json文件
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": [
"src/*"
]
}
}
}
这里也一定要修改package.json文件,修改启动指令
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
八、json-server 构建测试服务
- 安装json-server,
npm i -D json-server
; - 新建一个json文件存储数据;
- 在package.json添加启动命令;追加
"server": "json-server ./server/data.json --port 8888"
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"server": "json-server ./server/data.json --port 8888"
},
九、打包优化
1. 包体积可视化分析
安装插件
npm i source-map-explorer
package.json文件配置
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"analyze":"source-map-explorer 'build/static/js/*.js'"
},
执行analyze,执行后浏览器就会显示各部分的包体积。
npm run analyze
2.CDN配置
什么是CDN?
CDN是一种内容分发网络服务,当用户请求网站内容时,由离用户最近的服务器将缓存的资源内容传递给用户
- 哪些资源可以放到CDN服务器?
体积较大的非业务JS文件,比如react、react-dom
1.体积较大,需要利用CDN文件在浏览器的缓存特性,加快加载时间
2.非业务IS文件,不需要经常做变动,CDN不用频繁更新缓存 - 先在craco.config.js进行配置
const path = require("path")
module.exports = {
webpack: {
alias: {
'@': path.resolve(__dirname, 'src')
},
// 配置webpack
// 配置CDN
configure: (webpackConfig) => {
// webpackConfig自动注入的webpack配置对象
// 可以在这个函数中对它进行详细的自定义配置
// 只要最后return出去就行
let cdn = {
js: [],
css: []
}
// 只有生产环境才配置
whenProd(() => {
// key:需要不参与打包的具体的包
// value: cdn文件中 挂载于全局的变量名称 为了替换之前在开发环境下
// 通过import 导入的 react / react-dom
webpackConfig.externals = {
react: 'React',
'react-dom': 'ReactDOM'
}
// 配置现成的cdn 资源数组 现在是公共为了测试
// 实际开发的时候 用公司自己花钱买的cdn服务器
cdn = {
js: [
'https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js',
],
css: []
}
})
// 都是为了将来配置 htmlWebpackPlugin插件 将来在public/index.html注入
// cdn资源数组时 准备好的一些现成的资源
const { isFound, match } = getPlugin(
webpackConfig,
pluginByName('HtmlWebpackPlugin')
)
if (isFound) {
// 找到了HtmlWebpackPlugin的插件
match.userOptions.cdn = cdn
}
return webpackConfig
}
}
}
然后在index.html中配置
<body>
<div id="root"></div>
<!-- 加载第三发包的 CDN 链接 -->
<% htmlWebpackPlugin.options.cdn.js.forEach(cdnURL => { %>
<script src="<%= cdnURL %>"></script>
<% }) %>
</body>
文章内容为黑马程序员课程学习总结,大家感兴趣的也可以去B站看哦,老师讲的非常好