核心思想
UI=f(state)
用户界面UI 仅仅是应用走程序状态的一个函数(f)
第一代: 事件驱动
找到dom元素,去给元素绑定事件
js jquery
第二代 MVC MVVM
不在框架层去关注dom,而是关注数据
AnglarJs 早期vue
第三代 声明式编程
react心智模型
通过状态驱动视图更新
vite React ts
替代了webpack,颠覆性的前段构建工具,极大提升了前端开发体验,利用现在浏览器原生支持ES Module的特性,在开发阶段无需对所有代码进行打包,从而实现了几乎瞬时的服务器启动和快如
闪电的热模块更新(HMR),
安装
npm create vite@latest
接下来选择React作为开发框架,TS作为变体

下面默认

快捷创建
pnpm create vite name --template=react-ts
安装pnpm
npm install -g pnpm
依赖初始化
进入项目目录
pnpm i

启动
npm run dev 或者
pnpm dev

JSX语法
项目结构

vite.config.ts
最重要,用来构建react相关项目内容
tsconfig.json
tsconfig.app.json
配置前端用层,比如jsx
tsconfig.node.json
配置打包构建用node命令
package.json
依赖

eslint.config.ts
当前项目 规范化
src
jsx
在js中写html代码,是js的一种特殊语法,会被转化为js函数调用
React.createElement()
比如代码
<h1 className="title">Hello,React<h1>
会被转换成
React.createElement('h1',{className:'title'},'Hello,React');
举例

上面的main.tsx是源码,下面的倾斜的main.tsx是编译后的
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>,
)
编译后的,可以在里面看StrictMode,APP这个单词
会发现他们在jsxDEV这个函数中
import __vite__cjsImport0_react_jsxDevRuntime from "/node_modules/.vite/deps/react_jsx-dev-runtime.js?v=423528cb";
const jsxDEV = __vite__cjsImport0_react_jsxDevRuntime["jsxDEV"];
import __vite__cjsImport1_react from "/node_modules/.vite/deps/react.js?v=423528cb";
const StrictMode = __vite__cjsImport1_react["StrictMode"];
import __vite__cjsImport2_reactDom_client from "/node_modules/.vite/deps/react-dom_client.js?v=1fc37301";
const createRoot = __vite__cjsImport2_reactDom_client["createRoot"];
import "/src/index.css";
import App from "/src/App.tsx";
createRoot(document.getElementById("root")).render(/* @__PURE__ */
jsxDEV(StrictMode, {
children: /* @__PURE__ */
jsxDEV(App, {}, void 0, false, {
fileName: "D:/learn/react1/basicreact1/src/main.tsx",
lineNumber: 8,
columnNumber: 5
}, this)
}, void 0, false, {
fileName: "D:/learn/react1/basicreact1/src/main.tsx",
lineNumber: 7,
columnNumber: 3
}, this));
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJtYXBwaW5ncyI6IkFBT0k7QUFQSixTQUFTQSxrQkFBa0I7QUFDM0IsU0FBU0Msa0JBQWtCO0FBQzNCLE9BQU87QUFDUCxPQUFPQyxTQUFTO0FBRWhCRCxXQUFXRSxTQUFTQyxlQUFlLE1BQU0sQ0FBRSxFQUFFQztBQUFBQSxFQUMzQyx1QkFBQyxjQUNDLGlDQUFDLFNBQUQ7QUFBQTtBQUFBO0FBQUE7QUFBQSxTQUFJLEtBRE47QUFBQTtBQUFBO0FBQUE7QUFBQSxTQUVBO0FBQ0YiLCJuYW1lcyI6WyJTdHJpY3RNb2RlIiwiY3JlYXRlUm9vdCIsIkFwcCIsImRvY3VtZW50IiwiZ2V0RWxlbWVudEJ5SWQiLCJyZW5kZXIiXSwiaWdub3JlTGlzdCI6W10sInNvdXJjZXMiOlsibWFpbi50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgU3RyaWN0TW9kZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgY3JlYXRlUm9vdCB9IGZyb20gJ3JlYWN0LWRvbS9jbGllbnQnXG5pbXBvcnQgJy4vaW5kZXguY3NzJ1xuaW1wb3J0IEFwcCBmcm9tICcuL0FwcC50c3gnXG5cbmNyZWF0ZVJvb3QoZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3Jvb3QnKSEpLnJlbmRlcihcbiAgPFN0cmljdE1vZGU+XG4gICAgPEFwcCAvPlxuICA8L1N0cmljdE1vZGU+LFxuKVxuIl0sImZpbGUiOiJEOi9sZWFybi9yZWFjdDEvYmFzaWNyZWFjdDEvc3JjL21haW4udHN4In0=
正式环境构建
pnpm build
pnpm preview
查看
在js中正式环境使用的是jsx

jsx本质
本质是js,可以用{}在jsx中嵌入任何有效的js表达式,无论是变量,数学运算,还是函数调用
在JSX描述页面结构时,有几条核心规则:
一个组件返回的JSX必须拥有一个单一的根元素,如果想返回多个,外面需要用div包裹
也推荐<Fragment>不会生成实际元素
因为最终会生成js,避免关键字冲突,class要写成className, for要写成htmlFor
事件遵循驼峰命名onClick
函数式组件与Class组件
以后都用函数式,
最简App.tsx
import './App.css'
function App() {
return (
<div>
Hello, World!
</div>
)
}
export default App
下面是Class组件
import React from 'react'
import './App.css'
// function App() {
// return (
// <div>
// Hello, World!
// </div>
// )
// }
class App extends React.Component {
render() {
return (
<div>
Hello, World!
</div>
)
}
}
export default App
点击增加的实现
import React, { useState } from 'react'
import './App.css'
function App() {
// const count=0;
// 把count变成响应式的
const [count,setCount]=useState(0);
return <div>{count} </div>;
}
export default App
点击增加
import React, { useState } from 'react'
import './App.css'
function App() {
// const count=0;
// 把count变成响应式的
const [count,setCount]=useState(0);
return <div onClick={()=>setCount(count+1)}>{count} </div>;
}
export default App
改为Class
import React, { useState } from 'react'
import './App.css'
// function App() {
// // const count=0;
// // 把count变成响应式的
// const [count,setCount]=useState(0);
// return <div onClick={()=>setCount(count+1)}>{count} </div>;
// }
class App extends React.Component {
state = {
count: 0
};
render() {
return <div onClick={() => this.setState({ count: this.state.count + 1 })}>{this.state.count} </div>;
}
}
export default App
组件化开发核心
Props:外部给组件的传值
State:内部状态
Props
properties的缩写
相当于js中函数的参数
制作一个组件,无属性
// 具名导出,就是使用的时候也要用这个名字
export const HelloWorld = () => {
return <div>Hello, World!</div>;
}
// 默认导出
// const HelloWorld = () => {
// return <div>Hello, World!</div>;
// }
// export default HelloWorld;
最简单的返回一个组件,在App.tsx中
import React, { useState } from 'react'
import './App.css'
import { HelloWorld } from './components/1.hello'
// 函数式组件返回值就是实体内容
function App() {
return <HelloWorld />
}
export default App
题目2 字符串属性
传给组件一个字符串属性
import React, { useState } from 'react'
import './App.css'
import { HelloWorld } from './components/1.hello'
// 函数式组件返回值就是实体内容
function App() {
return <HelloWorld title='hihihi' />
}
export default App
组件接受一个字符串形参
//定义一个参数
interface HelloWorldProps {
title: string;
}
// 具名导出,就是使用的时候也要用这个名字
//注意形参中引用了上面的定义
export const HelloWorld = (props:HelloWorldProps) => {
//解构属性
const {title} = props;
return <div>Hello, World! {title}</div>;
}
// 默认导出
// const HelloWorld = () => {
// return <div>Hello, World!</div>;
// }
// export default HelloWorld;
题目三 函数当属性 render props
相当于子组件提供了一个类似vue的插槽,本身不确定什么内容,由父组件定义
Render函数
父组件
import React, { useState } from 'react'
import './App.css'
import { HelloWorld } from './components/1.hello'
// 函数式组件返回值就是实体内容
function App() {
return <HelloWorld title="hihihi" render={()=><div>你好</div>}/>
}
export default App
也可以增加颜色
import React, { useState } from 'react'
import './App.css'
import { HelloWorld } from './components/1.hello'
// 函数式组件返回值就是实体内容
function App() {
return <HelloWorld title="hihihi" render={()=><div style={{color: "red"}}>你好</div>}/>
}
export default App
函数
在定义中加了一个render?
返回 render?.()
import type React from "react";
//定义一个参数
interface HelloWorldProps {
title: string;
// 声明函数参数
render?:() => React.ReactNode; //返回的类型是React节点
}
// 具名导出,就是使用的时候也要用这个名字
//注意形参中引用了上面的定义
export const HelloWorld = (props:HelloWorldProps) => {
//解构属性
const {title, render} = props;
return <div>Hello, World! {title} {render?.()}</div>;
}
// 默认导出
// const HelloWorld = () => {
// return <div>Hello, World!</div>;
// }
// export default HelloWorld;
父组件多次调用子组件
return后加(<> </>)包起来
import React, { useState } from 'react'
import './App.css'
import { HelloWorld } from './components/1.hello'
// 函数式组件返回值就是实体内容
function App() {
return(
<>
<HelloWorld title="hihihi" render={()=><div style={{color: "red"}}>你好</div>}/>
<HelloWorld title="ok" render={()=><div style={{color: "red"}}>LOVE</div>}/>
</>
)
}
export default App
状态提升
上文中每个组件的count参数是独立的,如果需要共用就定义到
事件处理函数独立
import type React from "react";
import { useState } from "react";
//定义一个参数
interface HelloWorldProps {
title: string;
// 声明函数参数
// render?:() => React.ReactNode; //返回的类型是React节点
//可以接受参数,这样父组件可以使用子组件的参数
render?:(count: number) => React.ReactNode;
}
// 具名导出,就是使用的时候也要用这个名字
//注意形参中引用了上面的定义
export const HelloWorld = (props:HelloWorldProps) => {
//解构属性
const {title, render} = props;
const[count, setCount] = useState(0);
// 事件处理函数
const handleAdd=() => {
setCount(count + 1);
};
return (<div>Hello, World! {title} ---{count}
<button onClick={handleAdd}>+</button>
{render?.(count)}
</div>
);
}
// 默认导出
// const HelloWorld = () => {
// return <div>Hello, World!</div>;
// }
// export default HelloWorld;
回调父组件
当子组件值变化时,通过onChange回调父组件
import type React from "react";
import { useState } from "react";
//定义一个参数
interface HelloWorldProps {
title: string;
// 声明函数参数
// render?:() => React.ReactNode; //返回的类型是React节点
//可以接受参数,这样父组件可以使用子组件的参数
render?:(count: number) => React.ReactNode;
//当数据变化时,为了给父级传递一个回调函数
onChange?: (count: number) => void;
}
// 具名导出,就是使用的时候也要用这个名字
//注意形参中引用了上面的定义
export const HelloWorld = (props:HelloWorldProps) => {
//解构属性
const {title, render, onChange} = props;
const[count, setCount] = useState(0);
// 事件处理函数
const handleAdd=() => {
setCount(count + 1);
onChange?.(count + 1);
};
return (<div>Hello, World! {title} ---{count}
<button onClick={handleAdd}>+</button>
{render?.(count)}
</div>
);
}
// 默认导出
// const HelloWorld = () => {
// return <div>Hello, World!</div>;
// }
// export default HelloWorld;
父组件
在控制台输出count
import React, { useState } from 'react'
import './App.css'
import { HelloWorld } from './components/1.hello'
// 函数式组件返回值就是实体内容
function App() {
return(
<>
{/* 如下count其实是子组件内部的参数,但是父组件也可以使用了 */}
<HelloWorld
title="hihihi"
render={(count)=><div style={{color: "red"}}>你好{count}</div>}
// 当数据变化时,父组件应该做什么
onChange={(count)=>console.log(count)}
/>
<HelloWorld title="ok" render={(count)=><div style={{color: "red"}}>LOVE{count}</div>}/>
</>
)
}
export default App
状态管理
useState Hook
Props外部传入不可变,State则是组件内部自己管理的数据
useState接收一个参数,比如下面的0作为初始值,然后返回一个包含两个元素的数组,
通常通过js的数组解构来接收这两个值:属性(数据,状态变量)和方法(如何修改数据,更新函数)
import {useState} from 'react';
const Couter=()=>{
const[count,setCount]=useState(0);
}
当我们调用更新函数:
1)会计划一次对状态(上例中的状态)的更新,将新的状态值保存起来
2)触发该组件的一次重新渲染(re-rendor)
状态更新-->触发重新渲染-->使用新状态渲染UI 这个循环构成了React交互的核心
当页面点击时触发了handleAdd,进入触发setCount(),同时通过onChange把count暴露出去
const handleAdd=() => {
setCount(count + 1); //异步
onChange?.(count + 1); //因为上一步是异步,所以这里依然用count+1,同步则可直接用count了
};
对象
import { useState } from "react";
export const BasicState = () => {
//解构属性
const[info, setInfo] = useState(
{
age:0
}
)
// 事件处理函数
const handleAdd=() => {
// 这是错误的,不能直接修改
// info.age++
// setInfo(info);
// 正确的做法。其实是创建了一个新的对象给setInfo,以下是对象的写法
setInfo({
...info,
age:info.age+1 //age是个新的
})
//函数写法,这样就拿到了age前值
setInfo((prevInfo)=>({
...prevInfo,
age:prevInfo.age+1
}))
};
return (<div>{info.age}
<button onClick={handleAdd}>+</button>
</div>
);
}
父组件
import React, { useState } from 'react'
import './App.css'
import { HelloWorld } from './components/1.hello'
import { BasicState } from './components/2.basicSytate'
// 函数式组件返回值就是实体内容
function App() {
return(
<>
{/* 如下count其实是子组件内部的参数,但是父组件也可以使用了
<HelloWorld
title="hihihi"
render={(count)=><div style={{color: "red"}}>你好{count}</div>}
// 当数据变化时,父组件应该做什么
onChange={(count)=>console.log(count)}
/>
<HelloWorld title="ok" render={(count)=><div style={{color: "red"}}>LOVE{count}</div>}/> */}
<BasicState />
</>
)
}
export default App
列表渲染
import { useState } from "react";
export const List=() => {
//用数组的时候都用泛型定义下
const [list, setList] = useState<number[]>([]);
return (
<div>
{
list.map((item) => (
<div key={item}>{item}</div>
))
}
<button
onClick={() => {
//push比较传统
// list.push(list.length)
// setList([...list])
//也可以用如下方法
setList([...list, list.length])
}
}>追加元素</button>
</div>
)
}
父组件
import './App.css'
import { List } from './components/3.List'
// 函数式组件返回值就是实体内容
function App() {
return(
<>
<List/>
</>
)
}
export default App
条件渲染
可以使用三目运算,比如下面的偶数才显示
import { useState } from "react";
export const List=() => {
//用数组的时候都用泛型定义下
const [list, setList] = useState<number[]>([]);
//判断是不是奇数行odd是英文单词,作形容词时译为"奇怪的;奇数的;零散的;剩余的"
const isOdd = (n:number) => n % 2 === 1;
return (
<div>
{
// list.map((item) => (
// <div key={item}>{item}</div>
// ))
// 偶数才渲染
list.map((item)=>item%2===0?(
<div key={item}>{item}</div>
):null)
}
<button
onClick={() => {
//push比较传统
// list.push(list.length)
// setList([...list])
//也可以用如下方法
setList([...list, list.length])
}
}>追加元素</button>
</div>
)
}
列表的key
key是React识别列表中各个元素的身份证,当列表数据发生变化时(增删改排序),React会协调算法(Reconciliation /rekənsɪliˈeɪʃn/ 和解;协调;调解;和谐一致 )会通过key来高效对比新旧虚拟DOM树,根据key来判断哪些是新创建的,删除的,移动位置的
一个稳定且唯一的key 能帮助React最大已有DOM元素和组件实例,从而极大提升性能
不提供key控制台会警告,列表更新时可能有UI BUG,性能问题
不要使用数组索引作为Key,这是反模式,因为比如插入一个元素,所有后续元素索引都会改变,会让React误以为元素自身内容发生了大规模变化,引发不必要的重新渲染,甚至丢失组件内部的状态
理想的key值是数据项本身就带有的,比如数据库中的id
Hooks与生命周期
hooks定义时尽量用泛型进行约束,以免填错值
useEffect
监控某个值,创建副作用
import { useEffect, useState } from "react";
export const Hooks=()=>{
const [count,setCount] = useState(0);
const handleAdd=()=>{
// setCount(count+1)
//使用就近取值
setCount((prevCount)=>prevCount+1)
}
// 创建副作用 [count] 数据变化后处理这个副作用
useEffect(
() =>{
console.log(count),[count]
}
)
return(
<div>
<div>当前计数{count}</div>
<button onClick={handleAdd}>增加</button>
</div>
)
}
useEffect的三种写法
import { useEffect, useState } from "react";
export const Hooks=()=>{
const [count,setCount] = useState(0);
const handleAdd=()=>{
// setCount(count+1)
//使用就近取值
setCount((prevCount)=>prevCount+1)
}
// 第一种创建副作用 指定[count] 数据变化后处理这个副作用
useEffect(
() =>{
console.log(count),[count]
// 让标题变化
document.title = `当前计数${count}`;
}
)
// 第二种创建副作用 空数组表示只在组件挂载和卸载时执行---挂载就相当于订阅,组件卸载时取消订阅
useEffect(() =>{
console.log("空数组,订阅,组件挂载完成"); //类似于类组件的onComponentDidMount
//函数式编程中,取消订阅就是订阅的return一个函数
return ()=>{
console.log("取消订阅,组件卸载时执行"); //类似于类组件的onComponentWillUnmount
}},[]
)
// 第三种创建副作用 不传参表示每次组件更新都执行
useEffect(
() =>{
console.log("无参数,每次组件更新都执行"); //类似于类组件的onComponentDidUpdate
}
)
//订阅和取消订阅举例
// 在函数式编程,订阅和取消订阅就是标准化结构
// const unsubscribe =()=>{
// //取消订阅就是return 一个函数
// return ()=>{ console.log("取消订阅,组件卸载时执行");
// }
// }
return(
<div>
<div>当前计数{count}</div>
<button onClick={handleAdd}>增加</button>
</div>
)
}
父组件
import { useState } from 'react'
import './App.css'
import { Hooks } from './components/4.Hooks'
// 函数式组件返回值就是实体内容
function App() {
//为了隐藏组件,也是组件取消订阅
const [isShow,setIsShow]=useState(true)
const handleClick=()=>{
setIsShow(!isShow)
}
// 以下是三目运算符简写 {isShow?<Hooks/>:null}
return(
<>
{isShow && <Hooks/>}
<button onClick={handleClick}>{isShow?'隐藏Hooks组件':'显示Hooks组件'}</button>
</>
)
}
export default App
useRef
获取DOM或者缓存某一个数据(不是状态值,不会引起试图更新)
ref很常用,DIV等都有,只是没有使用
获取DOM元素
获取后,可以进行应用元素的方法,比如focus()
import { useEffect, useRef, useState } from "react";
export const UseRef=()=>{
const [count,setCount] = useState(0);
// const inputRef=useRef(null); //直接使用null 下面的聚焦focus会报错,因为null没有focus方法,但也会生效
// 如果想避免报错,可以给useRef传入一个泛型参数,告诉TS当前ref对象的current属性是什么类型
const inputRef=useRef<HTMLInputElement>(null);
const handleAdd=()=>{
// setCount(count+1)
//使用就近取值
setCount((prevCount)=>prevCount+1)
}
// 获取输入框输入的内容
useEffect(()=>{
// 获取输入这个dom元素
// console.log(inputRef.current);
// 获取输入这个dom元素并聚焦
console.log(inputRef.current?.focus());
})
return(
<div>
<div>当前计数{count}</div>
<button onClick={handleAdd}> 增加</button>
<input ref={inputRef}/>
</div>
)
}
存储值
不会引起数据更新
import { use, useEffect, useRef, useState } from "react"
export const USEREF2=()=>{
const [count,setCount]=useState<number>(0);
// 用于缓存的值,不会引起数据更新,增加泛型更严谨,不会被赋予其他类型的值
const isMounted=useRef<boolean>(false); //初始的时候肯定是没挂载
useEffect(()=>{
console.log('组件更新了');
console.log('isMounted',isMounted.current);
}
,[count])
useEffect(()=>{
console.log('组件挂载完成');
// 标志组件已经挂载了
isMounted.current=true;
},[]
)
return (
<div>
<div>当前计数{count}</div>
<button onClick={()=>setCount(count+1)}>增加</button>
</div>
)
}