react笔记
一、使用js创建虚拟dom
const VDOM = React.createElement(标签名, 标签属性, 标签内容)
const VDOM = React.createElement('h1', { id: 'title' }, 'hello react')
二、使用jsx创建虚拟dom
<div id="app"></div>
<!-- 引入react核心库 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<!-- 引入react-dom 支持react操作dom -->
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- babel 用于将jsx转为js -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
// 1. 创建虚拟dom
const VDOM = <h1>Hello React</h1>
// 2. 渲染虚拟dom到页面
// ReactDOM.render(虚拟dom, 容器)
ReactDOM.render(VDOM, document.querySelector('#app'))
</script>
const VDOM = (
<h1>
<span>Hello React</span>
</h1>
)
三、关于虚拟dom
- 本质是Object类型的对象(一般对象)
- 虚拟dom比较“轻”
- 虚拟dom最终会被react转化为真实dom,呈现在页面上
四、jsx语法规则
-
定义虚拟dom时,不要写引号
-
标签中混入js表达式时要用花括号
{}
(vue中是使用{{}}
,有点区别)const title = 'Hello React' const data = { name: '小天', age: 20 } const VDOM = ( <h1 id={title}> <span>{data.name}</span> </h1> )
-
样式的类名不要用
class
,要用className
-
内联样式,要用
style={{key:value}}
的形式写。
<span style={{ color: 'red', fontSize: '20px' }}>{data.name}</span>
- 只能有一个根标签
- 标签必须闭合
- 标签首字母
1. 若小写字母开头,则将标签转为html同名元素,若html中无该标签对应的同名元素,则报错。
2. 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
- jsx的注释
{/* <h1>hello react</h1> */}
五、react组件
1. 函数式组件(适用于简单组件的定义)
// 创建函数式组件
function Compontent () {
let VDOM = (
<div>
<h2>我是函数式组件</h2>
</div>
)
return VDOM
}
// 渲染组件到页面
ReactDOM.render(<Compontent />, document.querySelector('#app'))
2. 类式组件
2.1 类
// 创建一个Person类
class Person {
// 构造器方法
// 构造器的this是类的实例对象
constructor(name, age) {
this.name = name
this.age = age
}
// 类中可以直接写赋值语句,含义是:给Person实例对象添加一个属性,名为a,值为1
a = 1
// 一般方法
speak() {
// speak方法放在了Person类的原型对象上,供实例使用。this是指向调用他的Person实例
console.log(`我叫${this.name},我的年龄是${this.age}`)
}
}
// 创建一个Person的实例对象
let p = new Person('小天', 20)
// 调用
p.speak()
// 创建一个Student类,继承于Person类
class Student extends Person {
constructor(name, age, grade) {
super(name, age)
this.grade = grade
}
// 重写从父类继承过来的方法
speak() {
console.log(`我叫${this.name},我的年龄是${this.age},我读${this.grade}年级`)
}
}
let s = new Student('wifi', 20, '大三')
总结:
- 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写。
- 如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的。
- 类中定义的方法,都是放在了类的原型对象上,供实例去使用。
2.2 类中方法的this指向
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
study() {
console.log(this)
}
}
const p = new Person('xiaotian', 20)
p.study() // 通过实例调用的study方法
const x = p.study
x() // 函数的直接调用,this指向window,因为类中定义的方法自动开启了局部的严格模式,this值为undefined
2.3 类式组件(适用于复杂组件)
// 创建类式组件
class MyCompontent extends React.Component {
render() {
return <h1>hello react</h1>
}
}
3. 如何区分简单组件和复杂组件:是否有状态(state)
4. 组件实例的三大属性
4.1 属性1: state(状态)
class MyCompontent extends React.Component {
constructor(props) {
super(props)
this.state = {
isHot: false
}
}
render() {
return (
<div>
{ this.state.isHot ? '炎热' : '寒冷' }
</div>
)
}
}
4.2 react如何绑定事件
// 创建类式组件
class MyCompontent extends React.Component {
// 构造器只调用一次
constructor(props) {
super(props)
this.state = {
isHot: false
}
// 解决handleClick中的this指向问题
// 第二个handleClick是原型链上的,第一个是给原型上添加一个属性
this.handleClick = this.handleClick.bind(this)
}
// render至少调用1次,数据更新才会再次调用
render() {
return (
<div onClick={this.handleClick}>
{this.state.isHot ? '炎热' : '寒冷'}
</div>
)
}
// 由于handleClick是作为onClick的回调,所以不是通过实例调用的,是直接调用,this为undefined(看2.2 类中this的指向)
handleClick() {
console.log(this);
}
}
⚠️注意:状态不可直接更改,要通过内置的apisetState()
去修改
handleClick() {
// 更新数据不是替换,是合并
this.setState({
isHot: !this.state.isHot
})
console.log(this.state.isHot);
}
4.3 state简写方式
class MyCompontent extends React.Component {
// 初始化状态
state = { isHot: false }
render() {
return <h1 onClick={this.handleClick}>{ this.state.isHot ? '炎热' : '寒冷' }</h1>
}
// 自定义方法:要用赋值语句 + 箭头函数
handleClick = () => {
// 获取状态
const { isHot } = this.state
// 修改状态
this.setState({ isHot: !isHot })
}
}
state是组件对象的重要属性,值是对象(可以包含多个key-value的组合)
⚠️注意:
- 组件中render方法中的this为组件实例对象
- 组件自定义的方法中this为undefined,如何解决?
- 强制绑定this:通过函数对象的bind()
- 箭头函数
- 状态数据,不能直接修改或更新
4.4 类式组件使用props
class MyCompontent extends React.Component {
render() {
const { name, age } = this.props
return (
<div>{ name }{ age }</div>
)
}
}
const data = {
name: 'xiaotian',
age: 18
}
ReactDOM.render(<MyCompontent name={data.name} age={data.age} />, document.querySelector('#app'))
// 批量传递
ReactDOM.render(<MyCompontent {...data} />, document.querySelector('#app'))
4.5 展开运算符
// 展开运算符不能展开对象
let person = { name: 'xiaotian', age: 20 }
// 外层有{},可以复制一个对象
let person2 = { ...person }
// 但是在react组件传值的时候,并不是复制的一个对象
4.6 对props进行限制,和给默认值
class MyCompontent extends React.Component {
render() {
// props是只读的
const { name, age } = this.props
return (
<div>{ name }{ age }</div>
)
}
}
// 给props限制类型
MyCompontent.propsTypes = {
name: PropTypes.string.isRequired, // isRequired必传
age: PropTypes.number,
speak: ProTypes.func // 类型为函数
}
// 给props默认值
MyCompontent.defaultProps = {
name: 'xiaoming',
age: 18
}
ReactDOM.render(<MyCompontent name='xiaotian' age={18} speak="{speak}" />, document.querySelector('#app'))
const speak = () => {
...
}
在上述写法已被react移除,需要使用第三方库完成prop-types
- 安装库
prop-types
npm i prop-types
- 使用
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class GetMsg extends Component {
static propTypes = { // 小写
handleChange: PropTypes.func.isRequired // 大写
}
render() {
return (
<div>
<input type="text" onChange={(e) => {
this.props.handleChange(e.target.value)
}} />
</div>
)
}
}
4.7 props的简写方式
给类自身添加属性(不是给实例添加),可以写在外面
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
}
Person.data = 100
console.log(Person.data) // 100
写在里面要用关键字static
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
static data2 = 200
}
console.log(Person.data2) // 200
props的简写
class MyCompontent extends React.Component {
static propsTypes = {
name: PropTypes.string.isRequired, // isRequired必传
age: PropTypes.number
}
static defaultProps = {
name: 'xiaoming',
age: 18
}
state = {}
render() {
const { name, age } = this.props
return (
<div>{name}{age}</div>
)
}
}
ReactDOM.render(<MyCompontent name='xiaotian' age={18} />, document.querySelector('#app'))
4.8 类式组件的构造器(一般不写)
class MyCompontent extends React.Component {
constructor(props) {
// 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
super(props)
}
render() {
const { name, age } = this.props
return (
<div>{name}{age}</div>
)
}
}
4.9 函数式组件使用props
函数式组件只能使用props
function MyCompontent(props) {
console.log(props);
const { name, age } = props
return (
<div>{name}{age}</div>
)
}
// 给props限制类型
MyCompontent.propsTypes = {
name: PropTypes.string.isRequired, // isRequired必传
age: PropTypes.number,
speak: ProTypes.func // 类型为函数
}
// 给props默认值
MyCompontent.defaultProps = {
name: 'xiaoming',
age: 18
}
ReactDOM.render(<MyCompontent name='xiaotian' age={18} />,document.querySelector('#app'))
4.10 字符串形式的refs
(react新版本不推荐使用)
组件内的标签可以定义ref属性来标识自己
class MyCompontent extends React.Component {
showData = () => {
// 获取input的dom节点
this.refs.input
}
render() {
return (
<div>
<input type="text" ref="input"/>
<button ref="button" onClick={this.showData}>点击</button>
</div>
)
}
}
4.11 回调函数形式的ref
<input ref={(currentNode) => {console.log(currentNode)}} type="text"/>
// 将节点添加到类上
<input ref={(currentNode) => {this.input = currentNode}} type="text"/>
回调ref中调用次数的问题:当数据更新的时候,会被执行两次,第一次参数currentNode
为null
,第二次才是当前元素的dom节点。因为每次渲染时会创建一个新的函数实例(会重新调用render),清空旧的ref并设置新的ref,通过将ref的回调函数定义成class的绑定函数的方式可以避免该问题。例如:
class MyCompontent extends React.Component {
saveData = (currentNode) => {
this.input = currentNode
}
render() {
return (
<div>
<input ref={this.saveData} type="text" />
</div>
)
}
}
4.12 createRef的使用
React.createRef
调用后可以返回一个容器,该容器可以存储被ref所标识的节点。
该容器只能存放一个dom节点
class MyCompontent extends React.Component {
myRef = React.createRef()
myRef2 = React.createRef()
render() {
return (
<div>
<input ref={this.myRef} type="text" />
<input ref={this.myRef2} type="text" />
<button onClick={this.showData}>Click</button>
</div>
)
}
showData = () => {
console.log(this.myRef.current)
console.log(this.myRef2.current)
}
}
5. react中的事件处理
- 通过
onXxx
属性指定事件处理函数(注意大小写)- react使用的是自定义(合成)事件,而不是使用的原生dom事件(为了更好的兼容性)
- react中的事件是通过事件委托方式处理的(委托给组件最外层的元素,为了更高效)
- 通过
event.target
得到发送事件的dom元素对象(不要过度的使用ref) - react收集表单数据,包含表单的组件分类:
- 非受控组件
- 受控组件(类似于vue中的v-model绑定的,双向数据绑定)
6. 组件间的数据通信
- 父传子:通过
props
- 子传父:通过父组件向子组件
props
传递一个函数,由子组件向函数中传递参数,父组件接收
父组件
import React, { Component } from "react"
import Son from "../Son"
export default class Father extends Component {
sendData = (data) => {
console.log(data) // 子组件的数据
}
render() {
return (
<div>
<h1>父组件</h1>
<hr />
<Son sendData={this.sendData} />
</div>
)
}
}
子组件
import React, { Component } from "react"
export default class Son extends Component {
handleClick = () => {
this.props.sendData("子组件向父组件传递数据")
}
render() {
return (
<div>
<h1>子组件</h1>
<button onClick={this.handleClick}>点击按钮:子组件向父组件传递数据</button>
</div>
)
}
}
- 子孙组件通信(useContext)
useContext是一个 React Hook,可让您从组件读取和订阅上下文。
用法:const value = useContext(SomeContext)
1、参数的含义:SomeContext:使用createContext创建的上下文。上下文本身并不包含信息,它只代表您可以提供或从组件中读取的信息类型。
2、返回值的含义:value:useContext返回调用组件的上下文值,传递给最接近的SomeContext的值。
- 兄弟组件间通信:
消息订阅和与发布机制,react借助 PubSubJS
库实现
- 安装
npm install pubsub-js
- 使用
import PubSub from 'pubsub-js'
// 兄弟组件1:发布消息
PubSub.publish('message_name', { name: 'xiaotian', age: 20 })
// 兄弟组件2:订阅消息
PubSub.subscribe('message_name', (msg, data) => {
// msg:消息名 data:数据
...
})
六、高阶函数
-
高阶函数:如果一个函数符合下面2个规范中的任何一个,那就是高阶函数(例如:Promise、setTimeout)
- 若A函数,接收的参数是一个函数,那么A就可以称为高阶函数
- 若A函数,调用返回值是一个函数,那么A就可以称为高阶函数
-
函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码方式
function sun(a) { return (b) => { return (c) => { return a+b+c } } } const result = sum(1)(2)(3) console.log(result)
class MyCompontent extends React.Component {
state = {
data: ''
}
saveData = (event) => {
console.log(event);
this.setState({
data: event.target.value
})
}
render() {
return (
<div>
{/* this.saveData('123') 会直接调用该函数,而saveData的参数event就不是dom节点,而是123 */}
<input onChange={this.saveData('123')} type="text" />
</div>
)
}
}
解决办法:saveData
返回一个函数
class MyCompontent extends React.Component {
state = {
data: ''
}
// 函数柯里化 这个event可以不用写,是react自动生成的
saveData = (msg, [event]) => {
console.log(msg); // 123
return (event) => {
// event:dom节点
this.setState({
data: event.target.value
})
}
}
render() {
return (
<div>
{/* this.saveData('123') 会直接调用该函数,而saveData的参数event就不是dom节点,而是123 */}
<input onChange={this.saveData('123')} type="text" />
</div>
)
}
}
例子:
class MyCompontent extends React.Component {
state = {
username: '',
password: ''
}
saveData = (inputType) => {
// inputType: username or password
return (event) => {
this.setState({
// 使用[变量] 可以动态给属性赋值
[inputType]: event.target.value
})
console.log(this.state);
}
}
render() {
return (
<div>
用户:<input onChange={this.saveData('username')} type="text" />
密码:<input onChange={this.saveData('password')} type="text" />
</div>
)
}
}
七、react脚手架
1. 安装并使用
react脚手架库:create-react-app
- 全局安装:
npm i create-react-app -g
- 创建项目:
# 创建项目
create-react-app <项目名称>
# 进入项目目录
cd <项目名称>
# 启动项目
npm start
2. 脚手架配置代理
- 在
package.json
中添加配置,但只能配置一个代理
"proxy": "服务器地址(写到端口为止)"
- 在
src/setupProxy.js
(文件名为setupProxy,不能修改)中添加配置,可以配置多个代理
const { createProxyMiddleware } = require('http-proxy-middleware')
module.exports = (app) => {
app.use(
createProxyMiddleware('/api1', {
target: 'http://localhost:5000', // 带有/api1路径,就代理到localhost:5000
changeOrigin: true,
pathRewrite: {
'^/api1': '' // 重新路径,把api1这个前缀替换成''
// 前面的时候 请求路径会变成 http://localhost:5000/api1 ,但是这个请求路径是找不到的,需要把/api1,重新替换成''
}
})
),
app.use(
createProxyMiddleware('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {
'^/api2': ''
}
})
)
}
- 在组件里面请求
// http://localhost:3000 是react项目端口
// http://localhost:5000/getData 是服务器接口
axios.get('http://localhost:3000/api1/getData')
3. 配置路径别名@
- 路径解析配置(webpack):把
@/
解析成src/
配置步骤:
- 安装
craco
npm i @craco/craco -D
- 项目根目录下创建配置文件
craco.config.js
- 配置文件中添加路径解析配置
const path = require('path')
module.exports = {
// webpack配置
webpack: {
// 配置别名
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
- 包文件中配置启动和打包命令
"scripts": {
"start": "craco start",
"build": "craco build"
}
- vite配置
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path'
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
})
⚠️注意:如果path模块爆红,需要安装
@types/node
npm i @types/node -D
- vscode路径提示
在jsconfig.json
或tsconfig.json
的compilerOptions
字段添加下面内容:
{
...,
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
}
}
八、CSS
1. CSS-Module
样式隔离
在组件内通过import './xxx.css'
引入的样式是全局共享的,如果要实现样式的隔离,可以通过以下步骤:
-
新建
xxx.module.css
,后缀名要是.module.css
-
在组件内引入,在根标签通过
className={style.类名}
的形式就可以完成样式的隔离
import React from 'react'
import style from './index.module.css'
export default function about() {
console.log(style);
return (
<div className={style.box}>about</div>
)
}
可以查看log的结果:
2. css-in-js(styled-components)
常用库:styled-components
- 安装
npm install styled-components --save
npm install @types/styled-components -D
- 使用
1. 基本使用
import styled from "styled-components"
// 类样式当作组件使用 <Button>Normal</Button>
const Button = styled.button`
background: transparent;
border-radius: 3px;
border: 2px solid #BF4F74;
color: #BF4F74;
margin: 0 1em;
padding: 0.25em 1em;
`
const App = () => {
return <>
<Button>Normal</Button>
</>
}
export default App
const Button = styled.button`
background: "black";
color: "white";
`;
<Button>Normal</Button>
2. 嵌套使用
import React from "react";
import styled from "styled-components";
const Title = styled.h1`
font-size: 20px;
text-align: center;
color: red;
`;
export default function App() {
return <Title>Hello World</Title>;
}
3. 组件传参和条件渲染
import styled, { css } from "styled-components"
// 写法1
const Button = styled.button<{ dark?: boolean; }>`
${
props => props.dark && css`
background: black;
color: white;
`
}
`
// 写法2
const Button = styled.button<{ dark?: boolean; }>`
background: ${props => props.dark ? "black" : "white"};
color: ${props => props.dark ? "white" : "black"};
`
const App = () => {
return <>
<Button dark>按钮</Button>
</>
}
export default App
4. 全局样式
import { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
background: pink;
}
`
5. 样式组件的继承
const BoringButton = styled.button`
color: blue;
background-color: green;
`;
const CoolButton = styled(BoringButton)`
color: pink;
`;
6. 使用css辅助函数(公共样式)
在多个样式组件中使用通用的样式可以使用css辅助函数
import React from "react";
import styled, {css} from "styled-components";
const commonStyle = css`
color: white;
font-size: 20px;
`;
const Button = styled.button`
${commonStyle};
background-color: red;
`;
const AnotherButton = styled.button`
${commonStyle};
background-color: green;
`;
7. 元素属性
style.ts
import styled from 'styled-components'
export const AppStyled = styled.a.attrs({
href: 'www.baidu.com',
target: '_blank'
})`
color: red;
font-size: 30px;
`
App.tsx
import { AppStyled } from "./style";
export default function App() {
return <AppStyled>App</AppStyled>
}
- 效果
九、补充语法
1. 如何连续结构赋值
let obj = {
name: '张三',
age: 18,
info: {
address: '合肥'
}
}
const { info: { address } } = obj
console.log(address)
console.log(info) // 但是不能log info,报错