搭建开发环境
create-react-app(创建项目):npx create-react-app xxx(xxx 项目名)
解读项目目录
app.js
// 项目的根组件
// app被引入到index.js 被index.js里的核心代码 渲染到 public里的index.html里的root div上去
function App() {
return (
<div className="App">
this is APP
</div>
);
}
export default App;
index.js
// 整个项目的入口
// 下面两个是react两个必要的核心包
import React from 'react';
import ReactDOM from 'react-dom/client';
// 导入项目的根组件
import App from './App';
// 把根组件渲染到id为root的dom节点上(在public下的index里)
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
JSX
jsx:js+xml(html)
本质
jsx不是标准的js语法,浏览器不认识,需要通过解析工具(babel)做解析之后才能在浏览器中允许
想深入了解jsx (Babel · Babel (babeljs.io))多看看区别呗
jsx中使用js表达式
{}
注意:仅支持表达式哦!!if语句、switch语句、变量声明属于语句 不是表达式 不允许出现在{}中
小练习
实现列表渲染
const a = [
{name:'ju',age:12},
{name:'j',age:1},
{name:'u',age:2},
]
{/* 渲染a li标签 */}
<ul>
{a.map(item =><li key={item.id}>{item.name}</li>)}
</ul>
key的作用:提升列表更新性能
条件渲染
在react中,可以通过&&、三元表达式实现基础的条件渲染
const loading = false
可以根据函数调用的方式实现复杂的条件渲染
React基础的事件绑定
onClick={}
组件的基础使用
useState
他是一个ReactHook函数,它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果(双向绑定我认为的哦)
// 项目的根组件
// app被引入到index.js 被index.js里的核心代码 渲染到 public里的index.html里的root div上去
import { useState } from "react";
function App() {
{/* 使用useState实现一个计数器按钮 */}
const [count,setCount] =useState(0)
// 点击按钮事件回调
const handleClick = ()=>{
setCount(count+1)
}
return (
<div>
{/* 这是计数器的按钮 */}
<button onClick={handleClick}>计数器变化{count}</button>
</div>
);
}
export default App;
修改状态的规则
在react中,状态被认为是只读的,我们应该替换他 而不是去修改,直接修改没啥用,不会引起视图层变化
修改对象状态
对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改
// 项目的根组件
// app被引入到index.js 被index.js里的核心代码 渲染到 public里的index.html里的root div上去
import { useState } from "react";
function App() {
const [form,setForm] = useState({name:'ju',age:12})
// 点击按钮事件回调
const handleClick = ()=>{
setForm({
...form,
name:'luckly',
age:18
})
console.log(form)
}
return (
<div>
{/* 这是计数器的按钮 */}
<button onClick={handleClick}>{form.name}{form.age}</button>
</div>
);
}
export default App;
这就是新替换旧(覆盖)
基础样式处理
- 行内样式:style={{color:'red'}} 或者{类名}
- class类名控制:className='foo'
小工具:classnames
他是一个简单的js库,可以非常非常方便的通过条件动态控制class类名的显示(GitHub - JedWatson/classnames: A simple javascript utility for conditionally joining classNames together)
受控表单绑定
概念:使用react组件的状态(useState)控制表单的状态
获取Dom
在react组件中获取、操作dom,需要使用useRef钩子函数。
- 使用useRef创建ref对象,并和jsx绑定
- 在dom可用时,通过inputRef.current拿到dom对象
// 项目的根组件
// app被引入到index.js 被index.js里的核心代码 渲染到 public里的index.html里的root div上去
import { useRef } from "react";
function App() {
// null:他是个初始值,代表在没拿到dom之前 他是个null
const inputDom = useRef(null)
const click =()=>{
console.log(inputDom.current)
console.dir(inputDom.current)
}
return (
<div>
{/* ref={inputDom}:和input进行绑定 */}
<input type="text" ref={inputDom}/>
<button onClick={click}>点击获取input的Dom</button>
</div>
);
}
export default App;
组件之间通信
父传子
实现步骤:
- 父组件传递数据--在子组件身上绑定属性
- 子组件接收数据--子组件通过props参数接收数据
// 父组件传递数据--在子组件身上绑定属性
// 子组件接收数据--子组件通过props参数接收数据
// 子组件
function Son(props){
console.log(props)
return <div>Son---{props.name}</div>
}
function App() {
const name = 'fatherData'
return (
<div>
<Son name={name}></Son>
</div>
);
}
export default App;
props说明
- props可传递任意的数据(数字、字符串、布尔值、数组、对象、函数、jsx)
- props是只读对象(子组件只能读,不能修改,父组件的数据只能父组件修改!!跟vue一样)
好神奇呀!!!!!
特殊的prop——children
标签里面写的东西也是可以识别的。
例:当我们把内容嵌套在子组件标签中时,父组件会自动在名为children的prop属性中接收该内容
子传父
核心:在子组件里面调用父组件的函数并传递参数
import { useState } from "react"
// 通过解构赋值 接收父组件传递过来的函数 onGetSonData
function Son({onGetSonData}){
const name='sonData'
// 把子组件中的数据name当成实参传递过去 ,相当于去执行了父组件中的方法handlegetdata 把实参传递过去
return <div>son!!!-<button onClick={()=>onGetSonData(name)}>点击发送</button></div>
}
function App() {
const [fname,setFname] = useState('11')
// 在父组件里搞一个小函数
const handlegetdata = (name)=>{
console.log(name)
setFname(name)
}
return (
<div>
父亲!!——{fname}
{/* 把父组件里的小函数绑定过来传递给子 */}
<Son onGetSonData={handlegetdata} />
</div>
);
}
export default App;
使用状态提升实现兄弟组件通信
通过父组件进行兄弟之间的数据传递
步骤:
- A组件先通过子传父的方式把数据传给父组件APP
- App拿到数据后通过父传子的方式再传递给B组件
活脱脱一个二房东!!!!
import { useState } from "react"
// A组件先通过子传父的方式把数据传给父组件APP
// App拿到数据后通过父传子的方式再传递给B组件
function SonA({onGetMessage}){
const aMess = "this is dataA"
return <div>A
<button onClick={()=>{onGetMessage(aMess)}}>发送A数据到App</button>
</div>
}
function SonB(props){
console.log(props.aMess)
return <div>B--:{props.aMess}</div>
}
function App() {
const [aMess,setMess] = useState('')
// 拿A的数据
const aMessage =(aMess)=>{
console.log(aMess)
setMess(aMess)
}
return (
<div>
父中A的数据---:{aMess}
<SonA onGetMessage={aMessage}></SonA>
<SonB aMess={aMess}></SonB>
</div>
);
}
export default App;
跨级传递 (爷孙)
步骤:
- 使用createContext方法创建一个上下文对象Ctx
- 在顶层组件App中通过Ctx.provider组件提供数据
- 在底层组件B通过useContext钩子函数获取消费数据
import { createContext, useContext} from "react"
// 使用createContext方法创建一个上下文对象Ctx
const MsgContext = createContext()
// 在底层组件B通过useContext钩子函数获取消费数据
function A(){
return <div>A<B></B></div>
}
function B(){
const msg = useContext(MsgContext)
return <div>B {msg}</div>
}
function App() {
const msg = '传递的数据'
return (
<div>
{/* 在顶层组件App中通过Ctx.provider组件提供数据 */}
{/* value用来提供数据 要传递哪个数据就把哪个放过去 */}
<MsgContext.Provider value={msg}>父
<A></A>
</MsgContext.Provider>
</div>
);
}
export default App;
useEffect
他是一个react hook函数,用于在react组件中创建不是由事件引起而是渲染本身引起的操作,比如 发送ajax请求、更改Dom等。
// 第二个参数是依赖项数组
useEffect(()=>{
// 想做什么操作就放进来
},[])
第二个参数 的集中不同的情况
- 没有依赖项:代表组件初始化渲染+组件更新时执行。
- [ ] 空数组依赖:只在初始化渲染时执行一次。
- 添加特定的依赖项:代表 组件初始化渲染+特定依赖性变化时执行(有点监听的感觉)
清除副作用
它的意思是 比如:在useEffect中开启一个定时器,想在组件卸载时把定时器清掉,这个过程就叫清理副作用。
import { useEffect, useState } from "react";
function A(){
// 第二个参数是依赖性数组
useEffect(()=>{
// 实现副作用
console.log("执行了")
return ()=>{
// 清除副作用
console.log("清除了")
}
},[])
return <div>A</div>
}
function App() {
const [show,setMsg] = useState(true)
return (
<div>
<button onClick={()=>setMsg(false)}>点击变化{show}</button>
{show && <A></A>}
</div>
);
}
export default App;
点击之前:
点击之后:
自定义Hook实现
他的意思是 以use打头的函数,通过自定义hook函数可以用来实现逻辑的封装和复用
步骤:
- 声明一个以use打头的函数
- 在函数体内封装客服用的逻辑(只要是可复用用的逻辑)
- 把组件中用到的状态或者回调return出去(以对象或者数组)
- 在哪个组件中要用到这个逻辑,就执行这个函数,结构出来状态和回调进行使用
react hooks使用规则:
- 只能在组件中或者其他自定义hook函数中调用
- 只能在组件的顶层调用,不能嵌套在if、for、其他函数中
Redux
他是react最常用的集中管理工具,类似于vue的pinia(vuex),可以独立于框架运行(就是虽然是react最常用,但是他和react不绑定,可以自己跑)
使用步骤:
-
定义一个reducer函数(根据当前想做的事修改返回一个新的状态)
-
state :管理的数据初识状态
-
action:对象 type标记当前想要做什么样的修改(type === 增删改查这种操作)
-
-
使用createStore方法传入reducer函数 生成一个store实例对象
-
使用store实例 subscribe方法 订阅数据变化(数据一旦变化,可以得到通知)
-
使用store实例的dispatch方法提交action对象 触发数据变化(告诉reducer你想怎么改数据)
-
使用store实例的getState方法获取最新的状态数据更新到视图里
Redux的核心流程
分为三个核心概念:state、action、reducer
- state:一个对象 存放着我们管理的数据状态(数据库)
- action:一个对象 用来描述你想怎么去修改数据(指挥者)
- reducer:一个函数 根据action的描述生成一个新的state(真正的逻辑)
搭建环境
他得安装两个插件:redux Toolkit(简化书写 内有很多工具的工具包) 和 react-redux(用来连接redux和react 用来获取状态、更新状态)
npm i @reduxjs/toolkit react-redux
React与Redux实现
创建counterStore
import { createSlice } from "@reduxjs/toolkit";
// createSlice 用它来创建store
const counterStore = createSlice({
name:'counter',
// 初始化状态
initialState:{
count:0
},
// 编写修改数据的方法 支持直接修改
reducers:{
inscrement(state){
state.count++
},
decrement(state){
state.count--
}
}
})
// 解构出来actionCreater函数
const {inscrement,decrement} = counterStore.actions
// 获取reducer
const reducer = counterStore.reducer
// 以按需导出的方式导出actionCreater
export {inscrement,decrement}
// 以默认导出方式导出reducer
export default reducer
在入口文件index.js文件中 组合导出
// 把子模块导入进来 产出一个子模块 组合 =》导出
// 导入子模块reducer
import counteReducer from "./modules/counterStore";
import { configureStore } from "@reduxjs/toolkit";
const store = configureStore({
reducer:{
counter:counteReducer
}
})
export default store
为react注入store
React如何使用store中的数据
需要用到一个钩子函数useSelector,它的作用是把store中的数据映射到组件中:
react组件修改store中的数据
需要借助另一个hook函数 useDispatch ,它的作用是生成提交action对象的dispatch函数
// 项目的根组件
// app被引入到index.js 被index.js里的核心代码 渲染到 public里的index.html里的root div上去
import { useDispatch,useSelector } from "react-redux";
import { inscrement,decrement } from "./store/modules/counterStore";
function App() {
const {count} = useSelector(state => state.counter)
const dispatch = useDispatch()
return (
<div>
<button type="button" class="btn btn-default" onClick={()=>dispatch(decrement())}>-</button>
{count}
<button type="button" class="btn btn-default" onClick={()=>dispatch(inscrement() )}>+</button>
</div>
);
}
export default App;
如何要提交到action对象哇??? 执行store模块中导出的actionCreater方法
提交action传参实现需求
在reducer的同步修改方法中添加action对象参数,在调用actionCreater 的时候传递参数,参数会被传递到action对象的payload属性上!!!!
redux与react异步状态操作
异步操作步骤:
- 创建store的写法保持不变,配置好同步修改状态的写法
- 单独封装一个函数,在函数内部return一个新函数,在新函数中
- 封装异步请求获取数据
- 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交
- 组建中dispatch的写法保持不变
路由
前端路由:一个path对应一个组件,当浏览器访问一个path时,path对应的组件会在页面中进行渲染
安装路由:npm i react-router-dom
使用:
import { createBrowserRouter,RouterProvider } from 'react-router-dom';
import "./index.css"
// 创建router实例化对象并且配置路由对应关系
const router = createBrowserRouter([
{
path:"/login",
element:<div>这是登录 </div>
},
{
path:"/article",
element:<div>这是文章页</div>
}
])
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={router}></RouterProvider>
</React.StrictMode>
);
抽象路由模块
文章页:
const Article=()=>{
return <div>我是文章页面</div>
}
export default Article
登录页:
const Login=()=>{
return <div>我是登录页面</div>
}
export default Login
路由index.js:
import {createBrowserRouter} from "react-router-dom"
import Login from '../pages/Login'
import Article from "../pages/Article"
const router = createBrowserRouter([
{
path:"/login",
Component:Login
},
{
path:"/article",
Component:Article
}
])
export default router
路由导航
路由系统中多个路由之间 进行路由跳转,并且在跳转的同时需要传递参数进行通信。
声明式导航
<link to="/login">登录</link>:通过给组件的to属性指定要跳转到路由path,组件会被渲染为路由器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可
编程式导航
编程式导航是通过useNavigate钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,通过navigate方法传入地址path实现跳转
import { useNavigate } from "react-router-dom"
const Login=()=>{
const navigate = useNavigate()
return (
<div>我是登录页面
<button onClick={()=>{navigate('/article')}}>点击跳转文章页面</button>
</div>
)
}
export default Login
导航跳转传参
searchParams传参
传参数
接收参数
params传参
传参数
接收参数
别忘了占位符: ,这样param才能. 到东西
嵌套路由
在一级路由中又嵌套了其他路由 这种关系就叫做嵌套路由,嵌套至一级路由内的路由又称作二级路由。
实现步骤:
- 使用children属性配置路由嵌套关系
- 使用<Outlet/>组件配置二级路由渲染位置
在切换路由的时候会在<outlet>的地方进行渲染(有点占位置那味)
默认二级路由
当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true就行。
此行为的需求:进来这个页面的时候不需要点击去test,test就可以直接被渲染出来。
404路由
以*作为路由path 配置路由
路由的两种模式
history:url/login(没有# 好看一点)需要后端支持,底层原理(history+pushState事件)
后端需要配置nginx转发(不然路由找不到)
hash:url/#/login(有#)不需要后端支持 ,底层原理(监听hashChange事件)