1、 react 的基本使用
1.1 脚手架创建react 项目
npx create-react-app 项目名称
运行
npm start
1.2 脚手架写程序
// 1、导入 react
import React from 'react'
import ReactDOM from 'react-dom'
// React.createElement() 三个参数
// 参数1:htmlbi标签
// 参数2:标签的属性和属性值,以对象的形式出现
// 参数3:内容 或 节点
// 2、创建react 元素
const title = React.createElement('h1', null, 'hello react -- 脚手架')
// ReactDOM.render() 参数
// 参数1:创建的元素
// 参数2:需要渲染到哪个节点下面
// 3、渲染 react 元素
ReactDOM.render(title, document.getElementById('root'))
1.3 不同版本的写法
18.2 版本
// 1、导入 react
import React from 'react'
import ReactDOM from 'react-dom/client'
// 2、创建 react 元素
const list = (
<h1> hello world </h1>
)
// 获取根节点,渲染 数据
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(list);
17版本
// 1、导入 react
import React from 'react'
import ReactDOM from 'react-dom'
// 2、创建 react 元素
const list = (
<h1> hello world </h1>
)
// 3、渲染 react 元素
ReactDOM.render(list, document.querySelector('#root'))
2、JSX
2.1 JSX的基本使用
// 使用 JSX 创建react元素
const title = <h1>HEllo React</h1>
// 渲染 react 元素
ReactDOM.render(title, document.querySelector('#root'))
2.2 注意点
-
React元素的属性名使用驼峰命名法
-
特殊属性名:class -> className、for -> htmlFor、tabindex -> tabIndex 。
-
没有子节点的React元素可以用 /> 结束 。
-
推荐:使用小括号包裹 JSX ,从而避免 JS 中的自动插入分号陷阱。
2.3 JSX中使用 javascript 表达式
嵌入表达式 -- {} -- 可以写 js 语句
注意点:
单大括号中可以使用任意的 JavaScript 表达式
JSX 自身也是 JS 表达式
注意:JS 中的对象是一个例外,一般只会出现在 style 属性中
注意:不能在{}中出现语句(比如:if/for 等)
2.4 JSX的条件渲染
渲染的语句
// 控制渲染的语句
const isloading = false
// 条件渲染
const title = (
<h1>
条件渲染:
{ loadData() }
</h1>
)
// 渲染 react 元素
ReactDOM.render(title, document.querySelector('#root'))
1、使用 if else 语句
// if else 语句
const loadData = () => {
if(isloading) {
return <div>loading...</div>
}
return <div>数据紧挨在你完毕,此处显示架子啊后的数据</div>
}
2、三元表达式
// 三元表达式
const loadData = () => {
return isloading ? <div>laoding...</div> : <div>数据加载完毕...</div>
}
3、逻辑运算符
// 逻辑运算符
const loadData = () => {
return isloading && <div>loading...</div>
}
2.5 JSX 的样式处理
行内样式
<h1 className='title' style={ {color: 'red', backgroundColor: 'skyblue'} }>JSX样式处理</h1>
外部引入 css文件 -- 推介使用 className 方式给 JSX 添加样式
通过类名
<h1 className='title' style={ {color: 'red', backgroundColor: 'skyblue'} }>JSX样式处理</h1>
3 组件
3.1 组件的两种创建方式
3.1.1 使用函数创建组件
函数组件:使用 JS 的函数(或箭头函数)创建的组件
约定1:函数名称必须以大写字母开头
约定2:函数组件必须有返回值,表示该组件的结构
如果返回值为 null,表示不渲染任何内容
渲染函数组件
用函数名作为组件标签名 --- 单标签形式
root.render(<Hello />);
// 普通函数
function Hello() {
return (
<div>这是饿哦的第一个函数组件</div>
)
}
// 箭头函数
const Hello = () => <div>这是呵呵哈哈哈</div>
3.1.2 使用类创建组件
类组件:使用 ES6 的 class 创建的组件
约定1:类名称也必须以大写字母开头
约定2:类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
约定3:类组件必须提供 render() 方法
约定4:render() 方法必须有返回值,表示该组件的结构
// 类组件 -- 单标签渲染
class Hello extends React.Component {
render() {
return (
<div>这是我第一个类组件</div>
)
}
}
3.1.3 抽离为独立 js 组件
1. 创建Hello.js
2. 在 Hello.js 中导入React
// 导入 react
import React from 'react'
3. 创建组件(函数 或 类)
// 创建组件
class Hello extends React.Component {
render() {
return (
<div>这是我第一个类组件 -- 抽离的</div>
)
}
}
4. 在 Hello.js 中导出该组件
// 导入组件
export default Hello
5. 在 index.js 中导入 Hello 组件
// 导入 Hello 组件
import Hello from './js/Hello';
6. 渲染组件
// 渲染组件 -- 18 版本的
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Hello />);
4 React 事件处理
4.1 事件绑定
语法:on+事件名称={事件处理程序},比如:onClick={() => {}}
注意:React 事件采用驼峰命名法,比如:onMouseEnter、onFocus
// 类组件
class App extends React.Component {
// 事件处理程序
handleClick() {
console.log('单机事件');
}
render() {
return (
<button onClick={this.handleClick}>惦记我</button>
)
}
}
// 通过函数组件绑定事件
function App() {
// 事件处理程序
function handleClick() {
console.log('函数组件中的事件绑定');
}
return(
// 函数组件中是没有 this 的
<button onClick={handleClick}>dianwo</button>
)
}
4.2 事件对象 e
可以通过事件处理程序的参数获取到事件对象
React 中的事件对象叫做:合成事件(对象)
合成事件:兼容所有浏览器,无需担心跨浏览器兼容性问题
/* React 事件对象 */
class App extends React.Component {
handleClick(e) {
// 阻止浏览器的默认行为
e.preventDefault()
console.log('a标签的单机事件触发了')
}
render() {
return (
<a href="https://baidu.com" onClick={this.handleClick}>百度</a>
)
}
}
4.3 有状态组件和无状态组件
函数组件又叫做无状态组件,类组件又叫做有状态组件
状态(state)即数据
函数组件没有自己的状态,只负责数据展示(静)
类组件有自己的状态,负责更新 UI,让页面“动” 起来
4.4 组件中的 state 和 setState
4.4.1 state的基本使用
状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
state 的值是对象,表示一个组件中可以有多个数据
获取状态:this.state
/* state 的基本使用 */
class App extends React.Component {
// constructor() {
// super()
// // 初始化state
// this.state = {
// count: 0
// }
// }
// 简化的语法
state = {
count: 10
}
// 获取状态:this.state
render() {
return (
<div>
<h1>计数器:{ this.state.count }</h1>
</div>
)
}
}
状态即数据
状态是私有的,只能在组件内部使用
通过 this.state 来获取状态
4.4.2 setState()修改状态
状态是可变的
语法:this.setState({ 要修改的数据 })
注意:不要直接修改 state 中的值,这是错误的!!!
setState() 作用:1. 修改 state 2. 更新UI
思想:数据驱动视图
class App extends React.Component {
// 简化的语法
state = {
count: 10
}
// 获取状态:this.state
// 语法:this.setState({ 要修改的数据 })
render() {
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={() => {this.setState({count: this.state.count + 1})}}>+1</button>
</div>
)
}
}
4.5 从 JSX 中抽离事件处理程序
原因:事件处理程序中 this 的值为 undefined
+ 希望:this 指向组件实例(render方法中的this即为组件实例)
class App extends React.Component {
// constructor() {
// super()
// // 初始化state
// this.state = {
// count: 0
// }
// }
// 简化的语法
state = {
count: 10
}
// 抽离事件处理程序
onIncrement() {
//console.log('事件处理函数中的 this 指向', this); // undefined
this.setState({
count: this.state.count + 1
})
}
// 获取状态:this.state
render() {
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={this.onIncrement}>+1</button>
{/* <button onClick={() => {this.setState({count: this.state.count + 1})}}>+1</button> */}
</div>
)
}
}
4.5.1 解决抽离事件处理程序中的 this 指向
1、箭头函数
class App extends React.Component {
// 简化的语法
state = {
count: 10
}
// 事件处理程序
onIncrement() {
this.setState({
count: this.state.count + 1
})
}
// 获取状态:this.state
render() {
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={() => this.onIncrement()}>+1</button>
</div>
)
}
}
2、Function.prototype.bind()
class App extends React.Component {
constructor() {
super()
// 把 onIncrement 的 this 指向 这个类的 this
this.onIncrement = this.onIncrement.bind(this)
}
// 简化的语法
state = {
count: 10
}
// 事件处理程序
onIncrement() {
this.setState({
count: this.state.count + 1
})
}
// 获取状态:this.state
render() {
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={this.onIncrement}>+1</button>
</div>
)
}
}
3、class 的实例方法 --- 推介使用 利用箭头函数形式化的 class 实例方法
class App extends React.Component {
// 简化的语法
state = {
count: 10
}
// 事件处理程序 -- class 箭头函数的实例对象
onIncrement = () => {
console.log('事件处理函数中的 this 指向', this); // undefined
this.setState({
count: this.state.count + 1
})
}
// 获取状态:this.state
render() {
return (
<div>
<h1>计数器:{ this.state.count }</h1>
<button onClick={this.onIncrement}>+1</button>
</div>
)
}
}
5 表单处理
5.1 受控组件和非受控组件(DOM方式)
5.1.1 受控组件
class App extends React.Component {
// 简化的语法
state = {
txt: ''
}
handleChange = (e) => {
this.setState({
txt: e.target.value
})
}
render() {
return (
<div>
<input type='text' value={this.state.txt} onChange={this.handleChange}></input>
</div>
)
}
}
5.1.1.1 实例 -- 文本框 - 富文本框 - 下拉框 - 复选框(checked)
class App extends React.Component {
// 简化的语法
state = {
txt: '',
content: '',
city: 'bj',
isChecked: false
}
handleChange = (e) => {
// console.log(e.target.value);
// 利用事件对象
this.setState({
txt: e.target.value
})
}
handleContent = (e) => {
this.setState({
content: e.target.value
})
}
handleCity = e => {
this.setState({
city: e.target.value
})
}
// 处理复选框的变化
handleChecked = e => {
this.setState({
isChecked: e.target.checked
})
}
render() {
return (
<div>
{/* 文本框 */}
文本框
<input type='text' value={this.state.txt} onChange={this.handleChange}></input>
<br/>
富文本框
{/* 富文本框 */}
<textarea value={this.state.content} onChange={this.handleContent}></textarea>
<br/>
下拉框
<select value={this.state.city} onChange={this.handleCity}>
<option value='sh'>上海</option>
<option value='bj'>北京</option>
<option value='gz'>广州</option>
</select>
<br />
{/* 复选框 */}
复选框
<input type="checkbox" checked={this.state.isChecked} onChange={this.handleChecked}></input>
</div>
)
}
}
优化多表单元素
handleForm = e => {
// 获取当前的 DOM 对象
const target = e.target
// 根据类型获取值
const value = target.type === 'checkbox' ? target.checked : target.value
// 获取name
const name = target.name
this.setState({
// 根据 name 设置对应的 state
// [name] 类似于 {} 引用属性
[name]: value
})
}
5.1.2 非受控组件
/* 非受控组件 */
class App extends React.Component {
constructor() {
super()
// 1. 调用 React.createRef() 方法创建一个 ref 对象
this.txtRef = React.createRef()
}
// 获取文本框的值
getTxt = e => {
// 3. 通过 ref 对象获取到文本框的值
console.log('文本框的值:' ,this.txtRef.current.value);
}
render() {
return(
<div>
{/* // 2. 将创建好的 ref 对象添加到文本框中 */}
<input type='text' ref={this.txtRef}></input>
<button onClick={this.getTxt}>获取文本框的值</button>
</div>
)
}
}
6 组件高级
6.1 组件的 prop
1、组件是封闭的,要接收外部数据应该通过 props 来实现
2、props的作用:接收传递给组件的数据
3、传递数据:给组件标签添加属性
4、接收数据:函数组件通过参数props接收数据,类组件通过 this.props 接收数据
/* 接收数据 */
/* 函数组件 */
const App = (props) => {
// props 是一个对象
console.log(props);
return (
<div>
<h1>props: {props.name}</h1>
</div>
)
}
/* 类组件 */
class App extends React.Component {
render() {
console.log(this.props);
return (
<div>
<h1>props: {this.props.name}</h1>
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 传递数据
root.render(<App name="jack" age={18} />)
特点
1. 可以给组件传递任意类型的数据
2. props 是只读的对象,只能读取属性的值,无法修改对象
3. 注意:使用类组件时,如果写了构造函数,应该将 props 传递给 super(),否则,无法在构造函数中获取到 props!
/* 类组件 */
class App extends React.Component {
// 使用 constructoe 函数时 没有传递 props ,this.props 是 undefined
constructor(props) {
super(props)
console.log(this.props);
}
render() {
// render函数中可以获取到 this.props 对象
console.log(this.props);
return (
<div>
<h1>props: {this.props.name}</h1>
</div>
)
}
}
6.2 组件通讯
6.2.1 父组件 --> 子组件
1、父组件提供要传递的 state 数据
2、子组件标签提添加属性,值为 state 中的数据
3、子组件通过 props 接收数据
/* 父组件 */
class Parent extends React.Component {
// 1、父组件提供要传递的 state 数据
state = {
userName: '蓝'
}
render() {
return (
<div>
{/* 2、子组件标签提添加属性,值为 state 中的数据 */}
传递数据给子组件:<Child name={this.state.userName}/>
</div>
)
}
}
/* 子组件 */
class Child extends React.Component {
// 3、子组件通过 props 接收数据
render() {
return (
<div>子组件接收到的数据:{this.props.name}</div>
)
}
}
6.2.2 子组件 --> 父组件
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
1. 父组件提供一个回调函数(用于接收数据)
2. 将该函数作为属性的值,传递给子组件
class Parent extends React.Component {
state = {
parentMsg: ''
}
// 提供回调函数,用来接收数据
getChildMsg = data => {
console.log('接收到子组件传递过来的数据:', data);
this.setState({
parentMsg: data
})
}
render() {
return (
<div>
父组件: {this.state.parentMsg}
<Child getMsg={this.getChildMsg}/>
</div>
)
}
}
3. 子组件通过 props 调用回调函数
4. 将子组件的数据作为参数传递给回调函数
class Child extends React.Component {
state = {
msg: '哈哈哈'
}
handleClick = () => {
// 子组件调用父组件中传递过来的回调函数
this.props.getMsg(this.state.msg)
}
render() {
return (
<div>
子组件:
<button onClick={this.handleClick}>点击我,传递数据</button>
</div>
)
}
}
6.2.3 兄弟组件
1、将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
2、思想:状态提升
3、公共父组件职责:1. 提供共享状态 2. 提供操作共享状态的方法
4、要通讯的子组件只需通过 props 接收状态或操作状态的方法
class Counter extends React.Component {
// 提供共享状态
state = {
count: 0
}
// 提供修改状态的方法
onIncrement = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<Child1 count={this.state.count}/>
<Child2 onIncrement={this.onIncrement}/>
</div>
)
}
}
const Child1 = (props) => {
return <h1>计数器:{props.count}</h1>
}
const Child2 = (props) => {
return <button onClick={() => props.onIncrement()}>+1</button>
}
6.2.4 Context -- 跨组件传递数据
1. 调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。
2. 使用 Provider 组件作为父节点。
3. 设置 value 属性,表示要传递的数据。
4. 调用 Consumer 组件接收数据。
// 1、创建 context 得到两个组件
const {Provider, Consumer} = React.createContext()
// 2. 使用 Provider 组件作为父节点。
class App extends React.Component {
render() {
return (
<Provider value="pink">
<div>
<Node />
</div>
</Provider>
)
}
}
// 3. 设置 value 属性,表示要传递的数据。
<Provider value="pink">
<div>
<Node />
</div>
</Provider>
// 4. 调用 Consumer 组件接收数据
const Child = props => {
return (
<div>
<Consumer>
{
data => <span>我是子节点 --- {data}</span>
}
</Consumer>
</div>
)
}
6.3 props 深入
6.3.1 children 属性
1、 children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性
2、 children 属性与普通的props一样,值可以是任意值(文本、React元素、组件,甚至是函数)
class App extends React.Component {
render() {
console.log(this.props);
return (
<div>
<h1>组件标签的子节</h1>
{this.props.children}
</div>
)
}
}
// 文本节点
root.render(<App>我是子节点</App>)
// 标签节点
root.render(<App><p>我是一个p标签</p></App>)
// Text 组件
const Text = () => <button>我是button 标签</button>
// 组件节点
root.render(<App><Text /></App>)
class App extends React.Component {
render() {
console.log(this.props);
this.props.children()
return (
<div>
<h1>组件标签的子节</h1>
</div>
)
}
}
// 函数节点
root.render(<App>{() => console.log('这是一个函数子节点')}</App>)
6.3.2 props 校验
使用步骤
1. 安装包 prop-types (yarn add prop-types / npm i props-types)
2. 导入 prop-types 包
3. 使用组件名.propTypes = {} 来给组件的props添加校验规则
4. 校验规则通过 PropTypes 对象来指定
/* props 校验 */
import PropTypes from 'prop-types'
const App = props => {
const arr = props.colors
const lis = arr.map((item,index) => <li key={index}>{item}</li>)
return <ul>{lis}</ul>
}
// 添加 props 校验
App.propTypes ={
colors: PropTypes.array
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App colors={['red','blue','sky']} />)
约束规则
1. 常见类型:array、bool、func、number、object、string
2. React元素类型:element
3. 必填项:isRequired
4. 特定结构的对象:shape({ })
import PropTypes from 'prop-types'
const App = props => {
return (
<div>
<h1>props校验: </h1>
</div>
)
}
// 添加 props 校验
// 属性 a 的类型 数值(number)
// 属性 fn 的类型 函数(func) 并且为必填项
// 属性 tag 类型 React元素 (element)
// 属性 fiflter 类型 对象({area: '上海',price: 1999})
App.propTypes = {
a: PropTypes.number,
fn: PropTypes.func.isRequired,
tag: PropTypes.element,
filter: PropTypes.shape({
area: PropTypes.string,
price: PropTypes.number
})
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App fn={() => {} }/>)
props 的默认值
const App = props => {
console.log(props);
return (
<div>
<h1>props校验: </h1>
<h1>显示props的默认值:{props.pageSize}</h1>
</div>
)
}
// 添加props 默认值
App.defaultProps = {
pageSize: 10
}
const root = ReactDOM.createRoot(document.getElementById('root'))
// 没有传值的话,就以约束的默认值显示
root.render(<App pageSize={20}/>)
7 组件的生命周期
1、意义:组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等
2、组件的生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
3、生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数。
4、钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。
5、只有**类组件**才有生命周期。
1、创建时(挂载阶段)
1、constructor() -- 创建组件时,最先执行 -- 1. 初始化state 2. 为事件处理程序绑定this
2、render() -- 每次组件渲染都会触发 -- 渲染UI(注意:不能调用setState())
3、componentDidMount() -- 组件挂载(完成DOM渲染)后 -- 1. 发送网络请求 2. DOM操作
class App extends React.Component {
constructor(props) {
super(props)
console.warn('生命周期钩子函数: constructor');
// 初始化 state
this.state = {
count: 0
}
}
// 进行 DOM 操作
// 发送 ajax 请求,获取远程数据
componentDidMount() {
console.warn('生命周期钩子函数: componentDidMount');
}
// 打豆豆
handleClick = () => {
// this.setState({
// count: this.state.count + 1
// })
// 强制更新
this.forceUpdate()
}
render() {
console.warn('生命周期钩子函数: render');
return (
<div>
<Counter count={this.state.count} />
<button id="btn" onClick={this.handleClick}>打豆豆</button>
</div>
)
}
}
2. 更新时(更新阶段)
执行时机:1. setState() 2. forceUpdate() 3. 组件接收到新的props
说明:以上三者任意一种变化,组件就会重新渲染
执行顺序: render() -- componentDidUpdate()
render -- 每次组件渲染都会触发 -- 渲染UI(与 挂在阶段 是同一个render)
componentDidUpdate -- 组件更新(完成DOM渲染)后 -- 1 发送网络请求
2 DOM操作
注意:如果要setState() 必须放在一个if条件中
render()
handleClick = () => {
// this.setState({
// count: this.state.count + 1
// })
// 强制更新
this.forceUpdate()
}
componentDidUpdate()
// 注意:如果要调用 setState() 更新状态,必须要放在一个 if 条件中
// 因为: 如果直接调用 setState() 更新状态,也会导致递归更新
componentDidUpdate(provProps) {
console.warn('生命周期钩子函数: componentDidUpdate');
// 比较更新前后的 props 是否相同,来决定是否重新渲染组件
console.log('上一次的props:', provProps, '当前的props: ', this.props);
if (provProps.count !== this.props.count) {
this.setState()
// 发送 ajax 请求
}
// 获取 DOM
// const btn = document.getElementById('btn')
// console.log(btn.innerHTML);
}
3. 卸载时(卸载阶段)
componentWillUnmount() -- 组件卸载(从页面中消失) -- 执行清理工作(比如:清理定时器等)
8 render-props和高阶组件(HOC)
8.1 render props 模式
使用步骤
1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(1. 状态 2. 操作状态的方法)
2. 将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
3. 使用 props.render() 的返回值作为要渲染的内容
父组件提供一个回调函数--通过对子组件添加属性的回调函数(父组件提供回调函数) -- 子组件调用这个回调函数传递参数
class Mouse extends React.Component {
// 鼠标位置
state = {
x: 0,
y: 0
}
// 鼠标移动事件处理函数
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 监听鼠标移动事件
componentDidMount() {
window.addEventListener('mousemove',this.handleMouseMove)
}
render() {
return this.props.render(this.state)
}
}
class App extends React.Component {
render() {
return(
<div>
<h1>render props 模式</h1>
<Mouse render={(mouse) => {
return <p>鼠标位置: x:{mouse.x} , y:{mouse.y}</p>
}}/>
{/* children 模式 */}
<Mouse>{ mouse => { return <p>鼠标位置: x:{mouse.x} , y:{mouse.y}</p> } }</Mouse>
</div>
)
}
}
代码优化
- 推荐:给 render props 模式添加 props校验
// 组件的卸载周期函数
// 推介:在组件卸载时移除事件绑定
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
- 应该在组件卸载时解除 mousemove 事件绑定
import PropTypes from 'prop-types'
// 代码优化 -- 添加 props 校验
Mouse.propTypes = {
children: PropTypes.func.isRequired
}
8.2 高阶组件(HOC)
使用步骤
1. 创建一个函数,名称约定以 with 开头
2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
4. 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中
import React from "react"
import ReactDOM from 'react-dom/client'
/* 创建高阶组件 */
function withMouse(WrappedComponent) {
// 该组件提供复用的状态逻辑
class Mouse extends React.Component {
// 鼠标状态
state = {
x: 0,
y: 0
}
// 鼠标移动事件处理函数
handleMouseMove = e => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
// 控制鼠标状态的逻辑
componentDidMount() {
window.addEventListener('mousemove', this.handleMouseMove)
}
// 解绑事件
componentWillUnmount() {
window.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
return <WrappedComponent {...this.state}></WrappedComponent>
}
}
return Mouse
}
// 用来测试高阶组件
const Position = props => {
return (
<p>
鼠标当前的位置:x: {props.x},y: {props.y}
</p>
)
}
// 获取增强后的组件
const MousePosition = withMouse(Position)
class App extends React.Component {
render() {
return (
<div>
<h1>高阶组件</h1>
{/* 渲染增强后的组件 */}
<MousePosition></MousePosition>
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
9 React 原理揭秘
9.1 setState() 的说明
1、setState() 是异步更新数据的
2、注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()
3、可以多次调用 setState() ,只会触发一次重新渲染
推荐语法
推荐:使用 setState((state, props) => {}) 语法
参数state:表示最新的state
参数props:表示最新的props
import React from "react"
import ReactDOM from 'react-dom/client'
class App extends React.Component {
state = {
count: 0
}
// 推介的更新语法
handleChange = () => {
this.setState((state,props) => {
return {
count: state.count + 1
}
},
// 第二个参数:状态更新后并且重新渲染后,立即执行
() => {
console.log('状态更新完成:', this.state.count);
})
}
render() {
return (
<div>
<p>数值: {this.state.count}</p>
<button onClick={this.handleChange}>+1</button>
</div>
)
}
}
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App />)
第二个参数
场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法: setState(updater[, callback])
9.2 JSX 语法的转化过程
JSX 仅仅是 createElement() 方法的语法糖(简化语法)
JSX 语法被 @babel/preset-react 插件编译为 createElement() 方法
React 元素:是一个对象,用来描述你希望在屏幕上看到的内容
JSX 语法 -- > createElement() -- > React 元素
9.3 组件性能优化
减轻 state
减轻 state:只存储跟组件渲染相关的数据(比如:count / 列表数据 / loading 等)
注意:不用做渲染的数据不要放在 state 中,比如定时器 id等
对于这种需要在多个方法中用到的数据,应该放在 this 中
class Hello extends Component {
componentDidMount() {
// timerId存储到this中,而不是state中
this.timerId = setInterval(() => {}, 2000)
}
componentWillUnmount() {
clearInterval(this.timerId)
}
render() { … }
}
避免不必要的重新渲染
组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
问题:子组件没有任何变化时也会重新渲染
如何避免不必要的重新渲染呢?
解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate render)
// 钩子函数
shouldComponentUpdate(nextProps,nextState) {
// 返回false, 阻止组件重新渲染
// return false
// 最新的状态
console.log('最新的state: ', nextState);
// 更新前的状态
console.log('this.state: ',this.state);
return true
}
避免不必要的重新渲染
class Hello extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextState.number !== this.state.number
}
render() {…}
}
子组件的避免不必要的更新
class Hello extends Component {
shouldComponentUpdate(nextProps, nextState) {
return nextProps.number !== this.props.number
}
render() {…}
}
9.4 纯组件
纯组件:PureComponent 与 React.Component 功能相似
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {
render() {
return (
<div>纯组件</div>
)
}
}
说明:纯组件内部的对比是 shallow compare(浅层对比)
对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)
说明:纯组件内部的对比是 shallow compare(浅层对比)
对于引用类型来说:只比较对象的引用(地址)是否相同
注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!(示例)
state = { obj: { number: 0 } }
// 错误做法
state.obj.number = 2
setState({ obj: state.obj })
// PureComponent内部比较:
最新的state.obj === 上一次的state.obj // true,不重新渲染组件
// 正确!创建新数据
const newObj = {...state.obj, number: 2}
setState({ obj: newObj })
// 正确!创建新数据
// 不要用数组的push / unshift 等直接修改当前数组的的方法
// 而应该用 concat 或 slice 等这些返回新数组的方法
this.setState({
list: [...this.state.list, {新数据}]
})
9.5 虚拟 DOM 和 Diff 算法
React 更新视图的思想是:只要 state 变化就重新渲染视图
特点:思路非常清晰
问题:组件中只有一个 DOM 元素需要更新时,也得把整个组件的内容重新渲染到页面中? 不是
理想状态:部分更新,只更新变化的地方。
问题:React 是如何做到部分更新的? 虚拟 DOM 配合 Diff 算法
虚拟 DOM:本质上就是一个 JS 对象,用来描述你希望在屏幕上看到的内容(UI)。
执行过程
1. 初次渲染时,React 会根据初始state(Model),创建一个虚拟 DOM 对象(树)。
2. 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。
3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
5. 最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。