① React
React
- DOM
diff
算法:所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分反映在真实 DOM 上(可以极大提高网页的性能)
React 是一个视图层框架,而 vue 是一个完整的 MVVM 框架
- 特点
- 声明式设计
- 高性能 -- virtualDOM
- 组件化开发
- 单向响应的数据流
- jsx 扩展
1 使用
1.1 React 依赖 -- react & react-dom
//React在每个组件中都必须引入,哪怕你使用React变量
import React from 'react';
1.2 节点渲染与创建
1.2.1 React 渲染
- React 构建的应用通常只有一个根DOM节点
要将一个 React 元素渲染到根 DOM 节点中,需要将其传入
ReactDOM.render()

ReactDOM.render(template, targetDOM)
是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点
template
:可以是 HTM L标签或 React 组件targetDOM
:挂载点,必须为元素节点
const element = <h1>Hello, world</h1>
ReactDOM.render(element, document.getElementById('root'))
1.2.2 热更新
热更新 -- 自动刷新浏览器
- 原理:
websocket
1. 更新已渲染的元素
-
React 元素是
不可变对象
。一旦创建就无法更改其子元素或属性 -
更新 UI 唯一的方式就是创建一个全新的元素,并将其传入
ReactDOM.render()
function tick() {
const element = <h1>Hello, world</h1>
ReactDOM.render(element, document.getElementById('root'))
}
setInterval(tick, 1000)
tip: 大多数 React 应用只会调用一次
ReactDOM.render()
2. React 只更新它需要更新的部分
ReactDOM
会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使DOM
达到预期的状态
1.3 JSX
jsx 实现在 js 文件中编写 html 代码
因为 js 代码与 JSX 代码并不兼容,凡是使用 JSX 的 script 标签都需要加上type="text/babel"
tip: JSX 不是字符串
- 在 js 中编写 HTML 代码,在编译时
webpack
会通过特定的规则(loader
)编译 JSX 代码到 js
JSX 不是必须的,它只是
React.createElement(type,[props],[...children])
的语法糖,在我们应用 JSX 进行开发的时候,其实它最终会转化成React.createElement…
去创建元素
ReactDOM.render(
// JSX写法
<div id="container" className="home">
{ //<h1>Hello {username}</h1> }
<ul>
<li>data1</li>
<li>data2</li>
</ul>
<label htmlFor="username" style={ myStyle }>用户名:</label>
<input id="username" autoFocus onKeyDown={ ()=>console.log('keydown') }/>
</div>,
document.getElementById('root')
)
1.3.1 JSX 如何分辨 js、html 代码?
- 编译规则
- 尖括号内以 html 代码规则编译
- 花括号内以 js 代码规则编译
可以在 js 中编写 html 代码,在 html 中编写 js 代码要使用 {}
- JSX 的写法
- 关键字不能直接使用
- class -> className
- for -> htmlFor
- 多个单词的属性必须使用驼峰形式
- autofocus -> autoFocus
- onkeyup -> onKeyUp
- 必须闭合标签
- {} 只能使用 js 表达式
- style 属性值必须是一个对象
- {} 内不允许出现
let、var、const
等关键字
- 注释
- 关键字不能直接使用
在 JSX 中嵌入表达式
- 可以在
花括号
内放置任何有效的 js 表达式
const name = 'Josh Perez';
const element = <h1>Hello, { name }</h1>
ReactDOM.render(
element,
document.getElementById('root')
)
1.3.2 JSX 也是一个表达式
- 编译后,JSX 表达式也会被转为 js 函数调用,并且得到 js 对象
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
function getGreeting(user) {
if(user) {
return <h1>Hello, { formatName(user) }!</h1>
}
return <h1>Hello, Stranger.</h1>
}
1.3.3 JSX 特定属性
1. 引号
-> 将属性值指定为字符串字面量
const element = <div tabIndex="0"></div>
2. 花括号
-> 在属性值中插入一个 js 表达式
const element = <img src={ user.avatarUrl } />
3. 不可同时使用 引号
和 花括号
4. 警告
- JSX 语法更接近 js 而不是 html,所以
ReactDOM
使用驼峰命名
来定义属性名
class -> className tabindex -> tabIndex
1.3.4 JSX 防止注入攻击
ReactDOM
在渲染前会进行转义,有效防止XSS 攻击
const title = res.pot
// 直接使用是安全的
const element = <h1>{ title }</h1>
2 面向组件编程
2.1 组件定义
- 面向组件编程
在开发过程中,要善于观察和抽象。尤其是在项目前期,不要着急写代码,一定要观察项目的原型图或者设计稿,弄懂哪些部分是可以拆分成复用的公共组件的。这样做能让你后面的工作,事半功倍
2.1.1 规范
- 组件必须以 首字母大写 开头
- 只能有 唯一的顶级标签
2.1.2 分类
- react 组件:接收唯一带有数据的
props
对象与返回一个react元素
写入组件的方法会成为原型方法
1. 函数组件(无状态组件、受控组件、UI组件)
纯展示组件,这种组件只负责根据外部传入的 props 来展示,书写更简洁,执行效率更高
-
利用函数来定义一个组件,函数必须有返回值
-
特点
- 只根据传入的 props 属性展示不同的UI效果
- 组件不会被实例化,整体渲染性能得到提升--在实际开发过程中尽量使用函数组件
- 组件不能访问 this 对象
- 组件无法访问生命周期的方法
//定义
function MyComponent(props){
return <h1>函数组件</h1>
}
//使用
ReactDOM.render(
<MyComponent myname="zmoon" />,
document.getElementById('app')
);
2. 类组件(有状态组件、非受控组件、容器组件)
类继承组件有更丰富的特性(state状态、生命周期等)
- 利用 class 类创建的组件,继承自
React.Component
- 在类组件中可以使用
this
、生命周期
等特性 - 只有在
render
、constructor
、生命周期函数
中才有this
指向,其他自定义的方法默认没有this
指向 - 修改状态:重写一份数据并覆盖原来的数据
this.setState( )
- 改变函数
this
指向fn.bind
改变函数 fn 的this
指向,并返回一个新的函数,多次调用只有第一次生效
class TodoForm extends React.Component{
constructor(props){
super(props) // 使得 this.props 在其他自定义方法中也可以使用 否则只有 render 函数内可以使用
this.state = { keyword: '' }
}
render(){
return (
<div>
<input type="text" value={ this.state.keyword }/>
<button>添加</button>
</div>
)
}
}
3. 受控组件 & 非受控组件
- 对于受控组件来说,输入的值始终由 React 的 state 驱动
- 非受控组件,使用
ref
来从 dom 节点中获取表单数据,而不是为每个状态更新都编写数据处理函数
2.2 组件通讯 props
props
获取方式- 函数组件:通过参数
props
访问, 组件的第一个参数 - 类组件:通过
this.props
访问
- 函数组件:通过参数
tip:
props
是一个对象,包含使用组件时的所有属性,属性必须为只读的
tip: 所有 React 组件都必须像纯函数一样保护它们的props
不被更改
2.2.1 组件通讯 -- 父传子 props
- 父组件设置为子组件的属性值,并传递数据
- 子组件通过
props
使用
index.js
render(){
return (
<div>
<TodoForm addItem={ this.addItem } />
<TodoContent />
</div>
)
}
TodoForm.js
add() {
// 子组件内触发父组件传递的方法
this.props.addItem(this.state.keyword);
this.setState({
keyword: ""
})
this.keyword.focus()
}
2.2.2 深层次组件通讯 -- context 组件共享
- 逐层传递:操作繁琐、很难维护
context
组件共享
所谓
context
, 就是上下文环境, 某个组件只要往自己的context
里面放了某些状态, 这个组件之下的所有子组件都可以直接访问这个状态(context 好比组件的全局变量,能让所有子组件直接访问)

1. 创建 context
const MyContext = React.createContext('default')
MyContext.js
import React from 'react';
//default为默认值,当父组件不提供Provider时,使用默认值
const MyContext = React.createContext('default');
export default MyContext;
2. 父组件往 context 中写入共享数据 -- Provider
<MyContext.Provider value = {}></MyContext.Provider>
render(){
let { deleteItem, selectItem, completeItem } = this;
return (
<div>
<MyContext.Provider value={{ deleteItem, selectItem, completeItem }}>
{ /**...**/ }
</MyContext.Provider>
</div>
)
}
3. 子组件使用 context
- 类组件:contextType -- 仅类组件可用
SubComponent.contextType = MyContext;
render(){
return <Button theme={ this.context }>
}
- 函数组件:Consumer -- 通用
<MyContext.Consumer>{ value => { /*基于context进行渲染*/ }}</MyContext.Consumer>
return (
<MyContext.Consumer>
{
value => {
let { deleteItem, selectItem, completeItem } = value;
return /* 基于 context 值进行渲染*/
}
}
</MyContext.Consumer>
)
2.3 组件状态 state
React 会在 state 状态改变后自动执行组件中的 render 方法渲染视图
2.3.1 初始状态
class MyComponent extends React.Component {
constructor() {
super() // 这行代码不能少
this.state = {
isLiked: false
}
}
}
2.3.2 修改状态 setState
1. 格式:setState(nextState [,callback])
nextState
: 将要设置的新状态,该状态会和当前的 state 合并callback
: 可选参数,回调函数。该函数会在 setState 设置成功,且组件重新渲染后调用
2. 格式:setState(fn [,callback])
依赖上次
setState
的结果
fn(prevState)
this.setState(prevState => {
return { num: prevState.num+1 }
})
3. 多次 setState() 合并
- React 内部自动进行对比,得到最终结果后才渲染视图,所以并不需要担心多次进行 setState 会带来性能问题
PS:调用
setState()
并不会马上修改 state, 而是进入到一个更新队列里面,所以不能在组件内部通过this.state.xx = xx
直接修改状态,因为修改后会被队列中的setState()
替换(如下两次输出都为false)
console.log(this.state.isLiked); // false
this.setState({
isLiked: true
});
console.log(this.state.checked); //false
4. 强制更新组件: forceUpdate(callback)
forceUpdate()
方法会使组件调用自身的render()
方法重新渲染组件,组件的子组件也会调用自己的render()
,一般来说,应该尽量避免使用forceUpdate()
,而仅从this.props
和this.state
中读取状态并由 React 触发render()
调用
2.3.3 state VS props
- 一个组件的 state 中的数据可以通过 props 传给子组件
- 一个组件可以使用外部传入的 props 来初始化自己的 state
- state 是让组件控制自己的状态,props 是让外部对组件自己进行配置
2.3.4 正确使用 State
1. 不要直接修改 state
- 构造函数是唯一可以给
this.state
赋值的地方
// wrong
this.state.commit = 'Hello'
// Correct
this.setState({ commit: 'Hello' })
2. State 的更新可能是异步的
-
出于性能考虑,react 可能会把多个
setState()
调用合并成一个调用 -
要解决异步调用的问题,可以让
setState()
接收一个函数而不是一个对象,这个函数用上一个state
作为第一个参数,将此次更新被应用时的props
作为第二个参数
// wrong
this.setState({
counter: this.state.counter + this.props.increment
})
// Correct
this.setState((state, props) => {
counter: state.counter + props.increment
})
3. State 的更新会被合并
4. 数据是向下流动的
- 任何的
state
总是所属于特定的组件,而且从该state
派生的任何数据或 UI 只能影响树中“低于”它们的组件
2.4 高阶组件 HOC
- 高阶组件是一个纯函数
不依赖执行上下文,也不影响上下文的变量,输出只由输入决定
- 高阶组件的参数为组件,返回值为新组件
- 高阶组件是一种设计模式,类似于装饰器模式
2.4.1 使用场景
- 操纵
props
- 通过
ref
访问组件实例 - 组件状态提升
- 用其他元素包装组件
2.4.2 使用
1. 定义一个 HOC
//utils/withStorage.js
import React, { Component } from 'react'
const withStorage = WrappedComponent => {
return class extends Component{
componentWillMount() {
let data = localStorage.getItem('data')
this.setState({ data })
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />
}
}
}
export default withStorage
2. 在组件中使用 HOC
//components/Home.js
import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'
class Home extends Component{
render() {
//通过高阶组件可以直接获取data
return <h2>{this.props.data}</h2>
}
}
export default withStorage(Home)
3. ES7@
装饰器写法
import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'
@withStorage
class Home extends Component{
render() {
return <h2>{this.props.data}</h2>
}
}
export default Home
PS:需安装
@babel/plugin-proposal-decorators
插件
3 生命周期
生命周期:组件从创建到销毁的过程
3.1 阶段
针对类组件 函数组件没有生命周期
1. Initial 初始化阶段 -- 实例化
constructor
2. Mounting 阶段 -- 已插入真实 DOM
componentWillMount
-- 不推荐使用componentDidMount
-- ajax
3. Updating 阶段 -- 重新渲染
-
componentWillUpdate
-- 不推荐使用 -
componentDidUpdate
-- 慎重使用判断值修改时才发起 ajax 请求
componentWillUpdate(nextProps, nextState) {
//nextState: 将要更新的值
//this.state:当前的值
console.log('componentWillUpdate', nextProps, nextState, this.state);
}
componentDidUpdate(prevProps, prevState) {
console.log('componentDidUpdate', prevProps, prevState, this.state);
//判断值有修改之后才发起ajax请求
if(prevProps.username !== this.props.username){
console.log(123);
//发起ajax请求
//在此修改state要慎重,避免死循环
}
}
组件在什么情况下会被刷新?
props
更新state
更新- 强制刷新:
this.forceUpdate()
4. Unmounting 阶段 -- 已移除真实 DOM
componentWillUnmount
-- 清除定时器 取消ajax请求
3.2 特殊的生命周期函数 -- 父组件更新会导致子组件更新
componentWillReceiveProps
-- 不推荐使用
// 组件接收到了新的属性。新的属性会通过 nextProps 获取到。
// 组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。
componentWillReceiveProps(nextProps) {
console.log('componentWillReceiveProps', nextProps);
}
3.3 怎么阻止父组件的更新导致子组件也更新?
shouldComponentUpdate
条件生命周期,默认返回值 true, true 则渲染
一般用作性能优化 -- 不跑 render 则优化性能
- 慎重在
componentDidUpdate
shouldComponentUpdate
里面使用this.setState
, 会导致死循环
---> 把this.setState
放入条件判断语句
//节省开销,提高系统的性能
shouldComponentUpdate(nextProps, nextState) {
if(nextProps.age === this.props.age){
return false;
}
return true;
}
4 事件处理
- React 事件的命名采用 小驼峰式 而不是纯小写
- 使用 JSX 语法时需要传入一个 函数 作为事件处理函数而不是一个字符串
- JSX 不能通过返回
false
阻止默认行为,必需显式使用preventDefault
4.1 event 对象与事件处理函数传参
1. event
对象的获取
- 默认事件处理函数的最后一个参数
TodoForm.js
<input type="text"
value={ this.state.keyword }
onChange={ this.changekeyWord }
/>
changekeyWord(e) {
this.setState({
keyword : e.target.value
})
}
2. 传参
- bind
<button onClick={ completeItem.bind(this, item.id) }>完成</button>
3. 使用箭头函数调用
- event对象需要手动传递
- 可以传递其他参数
//定义
clickHandler(e,num){
console.log(e,num);
}
//使用
<button onClick={ e => this.clickHandler(e, 10) }>按钮</button>
4.2 this 指向的改变
<button onClick={ this.clickHandler }>按钮</button>
以上 clickHandler 被调用时,内部的 this 不指向组件实例,也不指向 button 元素,而是得到 undefined,如果需要用到 this 需要使用以下方式改变 this 指向
1. bind
- 初始化时
- 绑定事件时
2. 使用箭头函数
- 定义时
- 绑定事件时
5 条件渲染
1. 元素变量
- 使用变量来储存元素,有条件地渲染组件,而其他的渲染部分并不会因此而改变
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
<Greeting isLoggedIn={ false } />,
document.getElementById('root')
);
2. 与运算符 &&
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{ unreadMessages.length > 0 &&
<h2>
You have { unreadMessages.length } unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
)
3. 三目运算符
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
6 列表渲染
- 在 JSX 中不能直接渲染对象,但可以渲染数组,一般列表渲染都是使用
map
方法
TodoContent.js
<tbody>
{
datalist.map((item, index) => {
return <TodoItem
key={ item.id }
item={ item }
index={ index }
completeItem={ completeItem }
deleteItem={ deleteItem }
selectItem={ selectItem }
/>
})
}
</tbody>
用 key 提取组件
- 元素的
key
只有放在就近的数组上下文中才有意义
function ListItem(props) {
// 正确!这里不需要指定 key:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map(number =>
// 正确!key 应该在数组的上下文中被指定
<ListItem key={ number.toString() } value={ number } />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={ numbers } />,
document.getElementById('root')
);
key
只是在兄弟节点之间必须唯一
key 会传递信息给 React ,但不会传递给你的组件
const content = posts.map((post) =>
<Post
key={ post.id }
id={ post.id }
title={ post.title } />
);
// Post 组件可以读出 props.id,但是不能读出 props.key
7 获取真实DOM节点 refs
7.1 应用位置
- 应用在元素节点上:对节点的引用
- 应用在组件上:对组件实例的引用
函数组件不可使用 ref
7.2 适合使用 refs 的情况
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方 DOM 库
7.3 设置方式
- React.createRef()
- 回调 Refs
ref={ el => this.myRef=el }
// React.createRef()
this.btnSave = React.createRef();
<button ref={ this.btnSave }>保存</button>
//获取节点
this.btnSave.current
//回调 Refs
<button ref={el => { this.btnSave = el }}>保存</button>
// 获取节点
this.btnSave
8 组合 vs 继承
8.1 包含关系
1. 多数情况下
使用 children prop
来将子组件传递渲染
function FancyBorder(props) {
return (
<div className={ props.color }>
{ props.children }
</div>
)
}
使得别的组件可通过 jsx 嵌套,将任意组件作为子组件传递给它们
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1>Welcome</h1>
<p>Thank you for visiting our spacecraft!</p>
</FancyBorder>
)
}
2. 少数情况:将所需要内容传入 props,并使用相应的 prop
function SplitPane(props) {
return (
<div>
<div>{ props.left }</div>
<div>{ props.right }</div>
</div>
)
}
function App() {
return (
<SplitPane
left={ <Contacts /> }
right={ <Chat /> }
>
)
}
8.2 特例关系
1. 特殊组件可以通过 props
定制并渲染一般组件
function Dialog() {
return (
<FancyBorder color="blue">
<h1>{ props.title }</h1>
<p>{ props.message }</p>
</FancyBorder>
)
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!"
/>
)
}
2. 组合 + 特殊组件
function Dialog() {
return (
<FancyBorder color="blue">
<h1>{ props.title }</h1>
<p>{ props.message }</p>
{ props.children }
</FancyBorder>
)
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props)
this.handleChange = this.handleChange.bind(this)
this.handleSignUp = this.handleSignUp.bind(this)
this.state = { login: '' }
}
render() {
return (
<Dialog title="Welcome"
message="How should we refer to you?"
>
<input value={ this.state.login }
onChange={ this.handleChange } />
<button onClick={ this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
)
}
handleChange(e) {
this.setState({ login: e.target.value })
}
handleSignUp(e) {
talert(`Welcome abord, ${this.state.login}!`)
}
}
8.3 继承
-
如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 js 模块,如函数、对象或者类
-
组件可以直接引入(import)而无需通过 extend 继承它们
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)