React
一、React 基础
1. 什么是 React
React 是一个用于构建用户界面的前端框架。
它不是一个 MVC 的框架。它仅仅有 V 也就是视图层。也就是只负责视图的渲染。
2. React 的特点
-
声明式
只需要描述 UI 和写 HTML 是一样的
-
基于组件
组件是 React 最重要的内容。利用组件可以实现组合和复用
-
学习一次,随处使用
用处很多
3. React 的安装
安装命令:
npm i react react-dom
react 包是核心,提供创建元素、组件等功能
react-dom 包提供 DOM 相关功能
4. React 的使用
-
引入 react 和 react-dom 两个 js文件
<script src="./node_modules/react/umd/react.development.js"></script> <script src="./node_modules/react-dom/umd/react-dom.development.js"></script> -
创建 React 元素
const title = React.createElement('h1', null, 'Hello React') -
渲染React 元素到页面
<div id="root"></div> <script> // 第一个参数:创建的什么标签 // 第二个参数:标签上带什么属性 // 第三个参数:子标签(例如文本标签) const title = React.createElement('h1', null, 'Hello React') // 第一个参数:渲染的 React 元素 // 第二个参数:渲染的位置。挂到哪个元素上。 ReactDOM.render(title, document.getElementById('root')) </script>
5. React 脚手架
-
意义
-
脚手架是开发 现代Web 应用的必备。
-
充分利用 Webpack、Babel、ESLint 等工具辅助项目开发。
-
零配置,无需手动配置繁琐的工具即可使用。
-
关注业务,而不是工具配置。
-
-
初始化项目
初始化命令:
npx create-react-app my-app 或 npm init react-app my-app 或 yarn create react-app my-app进入项目目录:
cd my-app启动项目:
npm start
二、JSX
1. JSX 的基本使用
从 createElement() 变成 JSX
React.createElement(
'div',
{className: 'shopping-list'},
React.createElement('h1', null, 'Shopping List'),
React.createElement(
'ul',
null,
React.createElement('li', null, 'Instagram'),
React.createElement('li', null, 'WhatsApp')
)
)
<div className="shopping-list">
<h1>Shopping List</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
</ul>
</div>
JSX 是 react 的核心内容。
使用步骤:
-
使用 JSX 语法创建 react 元素:
const title = <h1>Hello JSX</h1> -
使用 ReactDOM.render() 方法渲染 React 元素到页面上:
ReactDOM.render(title, root)
注意
JSX 不是标准的 ECMAScript 语法,但是在脚手架中有 babel 进行编译。所以才能在浏览器中使用。
React 元素的属性名使用驼峰命名法
class => className
没有子节点的 React 元素可以用 /> 结束
推荐:使用小括号包裹 JSX,从而避免 JS 中的自动插入分号陷阱
2. JSX 中使用 JavaScirpt 表达式
嵌入 JS 表达式
const name = 'jack'
const dv = (
<div>你好,我叫:{name}</div>
)
注意
单大括号中可以使用任意的 JavaScript 表达式
JSX 自身也是 JS 表达式
不能在 {} 中出现语句(例如:if / for 等)
3. JSX 的条件渲染
使用if / else 或三元运算符或逻辑运算符来实现
const loadData = () => {
if (isLoading) {
return <div>数据加载中,请稍后...</div>
}
return (
<div>数据加载完成,此处显示加载后的数据</div>
)
}
const dv = (
<div>
{loadData()}
</div>
)
4. JSX 的列表渲染
如果这里是要渲染一组数据的话,可以利用数组的 map() 方法
const songs = [
{id: 1, name: '痴心绝对'},
{id: 2, name: '像我这样的人'},
{id: 3, name: '南山南'},
]
const list = (
<ul>{songs.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
)
注意
要个每个渲染的数据加一个 key 属性
key 属性的值要保证唯一
原则:map() 遍历谁,就给谁添加 key 属性
尽量避免使用索引作为 key 可以使用 id 值
5. JSX 的样式处理
-
行内样式 —— style
<h1 style={{ color: 'red', backgroundColor: 'skyblue' }}> JSX的样式处理 </h1> -
类名 —— className(推荐)
<h1 className="title"> JSX的样式处理 </h1>
三、React 组件基础
1. React 组件介绍
组件是 React 的一等公民,使用 React 就是在使用组件。
2. React 组件的两种创建方式
-
使用函数创建组件
函数组件:使用 JS 的函数(或箭头函数)创建的组件
约定1:函数名称必须以大写字母开头
约定2:函数组件必须有返回值,表示该组件的结构
如果没有返回值,可以写返回值为 null 表示不渲染任何内容。
function Hello(){ return ( <div>这是我的第一个函数组件</div> ) } const Hello = () => { return ( <div>这是我的第一个函数组件</div> ) } -
使用类创建组件
类组件:使用 ES6 的 class 创建的组件
约定1:类名称必须以大写字母开头
约定2:类组件应该继承 ReactComponent 父类,从而可以使用父类中提供发方法或属性
约定3:类租金啊必须提供 render() 方法
约定4:render() 方法必须有返回值,表示该组件的结构
class Hello extends React.Component { render(){ return <div>Hello class component!</div> } } ReactDOM.render(<Hello />, root) -
抽离为单独 JS 文件
项目多了之后,不同的组件要进行抽离。组件作为一个独立的个体,一般都会放到一个单独的 JS 文件中。
3. React 事件处理
-
事件绑定
注意
React 事件绑定语法与 DOM 事件语法相似
语法:on + 事件名称 = {事件处理程序},比如:onClick={() => {}}
React 事件采用驼峰命名法
在组件中绑定事件:
function App(){ function handleClick(){ console.log('单击事件触发了') } return ( <button onClick={handleClick}>点我</button> ) } class App extends React.Component{ handleClick(){ console.log('单击事件触发了') } render(){ return ( <button onClick={handleClick}>点我</buttn> ) } } -
事件对象
可以通过事件处理程序的参数获取到事件对象
React 中的事件对象叫做:合成事件(对象)
合成事件:兼容所有浏览器,无须担心跨浏览器兼容性问题
4. 有状态组件和无状态组件
简单来说就是。函数组件是无状态组件。类组件时有状态组件。这个状态是什么呢。就是数据。也就是 state。
5. 组件中的 state 和 setState
-
state 的基本使用
state 状态就是数组,这个数据是私有的。只能在组件内部才能使用
state 的值是对象,表示一个组件中可以有多个数据
两种初始化 state 的方法
// 利用类的 constructor constructor(){ super() this.state = { count: 0 } } // 或者简化语法直接初始化 state = { count: 0 }获取状态也就是获取数据:this.state
class Hello extends React.Component { // 简化语法 state= { count: 0 } render() { return ( <div>有状态组件,{this.state.count}</div> ) } } -
setState() 修改状态
状态是可变的
语法:this.state({要修改的数据})
不能直接去修改state
setState() 作用:1.修改 state 2.更新UI
// 正确 this.setState({ count: this.state.count + 1 }) // 错误 this.state.count += 1
6. 事件绑定 this 指向
-
箭头函数
利用箭头函数自身不绑定 this 的特点
render() 方法中的 this 为组件实例,可以获取到 setState()
class Hello extends React.Component { onIncrement() { this.setState({ … }) } render() { // 箭头函数中的this指向外部环境,此处为:render()方法 return ( <button onClick={() => this.onIncrement()}></button> ) } } -
Function.prototype.bind()
利用 ES5 中的 bind 方法,将事件处理程序中的 this 与组件实例绑定到一起
class Hello extends React.Component { constructor() { super() this.onIncrement = this.onIncrement.bind(this) } onIncrement() { this.setState({ … }) } render() { return ( <button onClick={this.onIncrement}></button> ) } } -
class 的实例方法
利用箭头函数形式的 class 实例方法
注意:该语法是实验性语法,但是,由于 babel 的存在可以直接使用
class Hello extends React.Component { onIncrement = () => { this.setState({ … }) } render() { return ( <button onClick={this.onIncrement}></button> ) } }
7. 表单处理
-
受控组件
HTML 中的表单元素是可输入的,也就是有自己的可变状态
而,React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改
React将 state 与表单元素值value绑定到一起,由 state 的值来控制表单元素的值
受控组件:其值受到 React 控制的表单元素
步骤:
- 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
- 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
state = {txt:''} <input type="text" value={this.state.txt} onClick={e => this.setState({txt:e.target.value})}></input>优化
给表单元素添加一个 name 属性 名称与 state 相同
这样就可以在多个表单的时候只写一个处理函数就好了
// 根据表单元素类型获取值 const value = target.type === 'checkbox' ? target.checked : target.value // 根据name设置对应state this.setState({ [name]: value }) <input type="text" name="txt" value={this.state.txt} onChange={this.handleForm} /> -
非受控组件(DOM 方式)
借助于 ref,使用原生 DOM 方式来获取表单元素值
ref的作用:获取 DOM 或组件
使用步骤:
-
调用 React.createRef() 方法创建一个 ref 对象
-
将创建好的 ref 对象添加到文本框中
-
通过 ref 对象获取到文本框的值 current
constructor() { super() this.txtRef = React.createRef() } <input type="text" ref={this.txtRef} /> console.log(this.txtRef.current.value)
-
四、React 组件进阶
1. 组件通讯介绍
组件时独立且封闭的单元,默认情况下,只能使用组件中自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享一些数据。所以我们要打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯。
2. 组件的 props
组件是封闭的,要接收外部数据应该通过 props 来实现
props 的作用:接收传递给组件的数据
传递数据:给组件标签添加属性
接收数据:函数组件通过参数 props 接收数据,类组件通过 this.props 接收数据
function Hello (props){
console.log(props)
return (
<div>接收到数据: {props.name}</div>
)
}
class Hello extends React.Component{
render(){
return (
<div>接收到数据: {this.props.name}</div>
)
}
}
<Hello name='jack' age={19} />
特点
可以给组件传递任意类型的数据
props 是只读的对象,只能读取属性的值,无法修改对象
注意:使用类组件时,如果写了构造函数,应该将props 传递给 super(),否则,无法在构造函数中获取到 props
class Hello extends React.Component { constructor(props) { super(props) } render() { return <div>接收到的数据:{this.props.age}</div> } }
3. 组件通讯的三种方式
-
父组件 -> 子组件
- 父组件提供要传递的 state 数据
- 给子组件标签添加属性,值为 state 中的数据
- 子组件中通过 props 接收父组件中传递的数据
class Parent extends React.Component { state = { lastName: '王' } render() { return ( <div> 传递数据给子组件:<Child name={this.state.lastName} /> </div> ) } } function Child(props){ return <div>子组件接收到数据:{props.name}</div> } -
子组件 -> 父组件
思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。
- 父组件提供一个回调函数(用于接收数据)
- 将该函数作为属性的值,传递给子组件
class Parent extends React.Component{ getChildMsg = (msg) =>{ console.log('接收到子组件数据', msg) } render(){ return( <div> 子组件:<Child getMsg={this.getChildMsg} /> </div> ) } } -
兄弟组件
将共享状态提升到最近的公共组件中,由共同的父组件管理这个状态
思想:状态提升
公共父组件职责:1.提供共享状态 2.提供操作共享状态的方法
要通讯的子组件子需要通过 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 onIncremnet={this.onIncrement} /> </div> ) } } const Child1 = (props) => { return <h1>计数器:{props.count}</h1> } const Child2 = (props) => { return <button onClick={props.onIncremnet()}>+1</button> }
4. Context
如果要跨越很多级子组件的时候进行数据传递怎么传递呢。
- 使用 props 一层一层向下传递(过于繁琐)
- 使用 Context
使用步骤
-
调用 React.createContext() 创建 Provider(提供数据)和 Consumer(消费数据)两个组件。
-
使用 Provider 组件作为父节点。
-
设置 value 属性,表示要传递的数据。
-
调用 Consumer 组件接收数据
// 创建context得到两个组件 const { Provider, Consumer } = React.createContext() class App extends React.Component { render() { return ( <Provider value="pink"> <div className="app"> <Node /> </div> </Provider> ) } } const Node = props => { return ( <div className="node"> <SubNode /> </div> ) } const SubNode = props => { return ( <div className="subnode"> <Child /> </div> ) } const Child = props => { return ( <div className="child"> <Consumer>{data => <span>我是子节点 -- {data}</span>}</Consumer> </div> ) }
5. props 深入
-
children 属性
children 属性:表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性
children 属性与普通的 props 一样,值可以是任意值(文本、React 元素、组件,甚至是函数)
function Hello(props) { return ( <div> 组件的子节点:{props.children} </div> ) } <Hello>我是子节点</Hello> -
props 校验
props 是从外边进入到组件内的,无法保证传进来的是什么格式的数据
如果数据有问题,组件内部就会报错
所以问题在于什么呢?问题在于组件的使用人。不知道自己明确的错误原因
所以我们使用 props 校验,在创建组件的时候,就指定 props 的类型和格式等。
作用:就是捕捉使用组件时候 props 导致的错误。给出明确的错误提示,增强组件的健壮性
使用步骤
-
安装包 prop-types(npm i prop-types)
-
导入prop-types包
-
使用组件名.propTypes={} 来给组件的 props 添加校验规则
-
校验规则通过 PropTypes 对象来指定
import PropTypes from 'prop-types' function App(props) { return ( <h1>Hi, {props.colors}</h1> ) } App.propTypes = { // 约定colors属性为array类型 // 如果类型不对,则报出明确错误,便于分析错误原因 colors: PropTypes.array }
约束规则
-
常见类型:array、bool、func、number、object、string
-
React 元素类型:element
-
必填项:isRequired
-
特定结构的对象:shape({})
// 常见类型 optionalFunc: PropTypes.func, // 必选 requiredFunc: PropTypes.func.isRequired, // 特定结构的对象 optionalObjectWithShape: PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number })
-
-
props 的默认值(defaultProps)
在未传入 props 的时候生效。
function App(props) { return ( <div> 此处展示props的默认值:{props.pageSize} </div> ) } // 设置默认值 App.defaultProps = { pageSize: 10 } // 不传入pageSize属性 <App />
6. 组件的声明周期
组件是声明周期从创建到挂载再到卸载。
每一个阶段都有对应的声明周期的钩子函数。
只有 类组件 才有声明周期
生命周期的阶段
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pu4UTVUG-1666060510360)(https://wanganyi-notes.oss-cn-beijing.aliyuncs.com/img/生命周期.png)]
-
创建时(挂载阶段)
执行时机:组件创建时(页面加载时)
执行顺序:constructor() --> render() --> componentDidMount

-
更新时(更新阶段)
执行时机:1.setState() 2.forceUpdate() 3.组件接收到新的 props
说明:以上三者任意一种变化,组件就会重新渲染
执行顺序:render() --> componentDidUpdate()

-
卸载时(卸载阶段)
执行时机:组件从页面消失
常用于:清除定时器。清理手动加载的监听事件。

7. render-props
render-props 模式
使用步骤
-
创建 Mouse 组件,在组件中提供复用的状态逻辑代码
-
将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
-
使用 props.render() 的返回值作为要渲染的内容
// 创建 Mouse 组件 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>鼠标位置:{mouse.x} {mouse.y}</p>)}} /> </div> ) } }
chidren 代替 render 属性
注意:并不是该模式叫 render props 就必须使用名为 render 的 prop,实际上可以使用任意名称的 prop
<Mouse>
{({x, y}) => <p>鼠标的位置是 {x},{y}</p> }
</Mouse>
// 组件内部:
this.props.children(this.state)
8. 高阶组件
概述
- 目的:实现状态逻辑复用
- 采用包装(修饰)模式
思路分析
-
高阶组件(HOC、Higher-Order Component) 是一个函数,接收要包装的组件,返回增强后的组件
-
高阶组件内部创建了一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件
WrappedComponentconst EnhancedComponent = withHOC(WrappedComponent) class Mouse extends React.Component{ render(){ return <WrappedComponent {...this.state} /> } }
使用步骤
-
创建一个函数,名称约定以with开头
-
指定函数参数,参数应该以大写字母开头
-
在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
-
在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
-
调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面
包装函数
// 定义一个函数,在函数内部创建一个相应类组件 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() { // 在render函数里面返回传递过来的组件,把当前组件的状态设置进去 return <WrappedComponent {...this.state} /> } } return Mouse }哪个组件需要加强,通过调用
withMouse这个函数,然后把返回的值设置到父组件中即可function Position(props) { return ( <p> X:{props.x} Y:{props.y} </p> ) } // 把position 组件来进行包装 let MousePosition = withMouse(Position) class App extends React.Component { constructor(props) { super(props) } render() { return ( <div> 高阶组件 <MousePosition></MousePosition> </div> ) } }
设置displayName
-
使用高阶组件存在的问题:得到的两个组件名称相同
-
原因:默认情况下,React使用组件名称作为 displayName
-
解决方式:为 高阶组件 设置 displayName 便于调试时区分不同的组件
-
displayName的作用:用于设置调试信息(React Developer Tools信息)
-
设置方式:
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}` function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component' }
传递props
-
问题:props丢失
-
原因:高阶组件没有往下传递props
-
解决方式:渲染 WrappedComponent 时,将 state 和 this.props 一起传递给组件
<WrappedComponent {...this.state} {...this.props} />
五、 React 原理
1. setState() 的说明
-
更新数据
setState() 是异步更新数据的
注意:使用该语法的时候,后面的 setState() 不要依赖前面的 setState()
可以多次调用 setState(),只会触发一次重新渲染
this.state = { count:0 } this.setState({ count:this.state.count +1 }) console.log(this.state.count) // 0 this.setState({ count:this.state.count +1 }) console.log(this.state.count) // 0 // state.count 最终还是,虽然加了两次 -
推荐语法
既然存在上面的问题。所以推荐:
使用 setState((state, props) => {}) 语法
参数 state:表示最新的 state
参数 props:表示最新的 props
this.state = { count:0 } this.setState((state, props) => { retrun{ count:this.state.count +1 }, () => { console.log('这个回调函数会在状态更新后并且重新渲染后立即执行' + this.state.count) // 此时这时候打印的count就是1了 } }) console.log(this.state.count) // 0 this.setState((state, props) => { retrun{ count:this.state.count +1 } }) console.log(this.state.count) // 0 // state.count 最后变成了 2注意:这样写还是异步的
2. JSX 语法的转化过程
其实 JSX 语法就是 createElement() 方法的语法糖
我们书写的 JSX 语法最终会被 @babel/preset-react 插件编译为 createElement() 方法
React 元素:是一个对象,用来描述你希望在屏幕上看到的内容

3. 组件更新机制
我们学 setState() 的时候,说它有两个作用:1.修改 state 2.更新组件(UI)
这个过程是什么样子的呢?
父组件重新渲染时,也会重新渲染子组件。但只会渲染当前组件子树(当前组件及其所有子组件)

4. 组件性能优化
-
减轻 state
在初始化 state 的时候,里面只储存与组件渲染相关的数据
注意:不用做渲染的数据不要放在 state 中
对于在多个方法中要用到的数据,应该放在 this 中
class Hello extends Component { constructor() { super() state = { count: 0 } } componentDidMount() { // timerId存储到this中,而不是state中 this.timerId = setInterval(() => {}, 2000) } componentWillUnmount() { clearInterval(this.timerId) } render() { … } } -
避免不必要的重新渲染
父组件更新会引起子组件更新,这种思路是很清晰的。
但是一个父组件的更新导致子组件更新,这个子组件里面的 UI 和 状态没有改变。我们还将它更新了。这是我们不想做的。
解决方法:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
这个函数可以通过返回值判断是否要重新渲染,返回 true 表示重新渲染,false 表示不重新渲染
触发时机:更新阶段的钩子函数,组件重新渲染之前执行(shouldComponentUpdate -> render)
class Hello extends Component { shouldComponentUpdate() { // 根据条件,决定是否重新渲染组件 return false } render() {…} }例子:
import React from 'react' import ReactDOM from 'react-dom' /* 组件性能优化: */ // 生成随机数 class App extends React.Component { state = { number: 0 } handleClick = () => { this.setState(() => { return { number: Math.floor(Math.random() * 3) } }) } // 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染 shouldComponentUpdate(nextProps, nextState) { console.log('最新状态:', nextState, ', 当前状态:', this.state) return nextState.number !== this.state.number // if (nextState.number !== this.state.number) { // return true // } // return false // if (nextState.number === this.state.number) { // return false // } // return true } render() { console.log('render') return ( <div> <h1>随机数:{this.state.number}</h1> <button onClick={this.handleClick}>重新生成</button> </div> ) } } ReactDOM.render(<App />, document.getElementById('root'))import React from 'react' import ReactDOM from 'react-dom' /* 组件性能优化: */ // 生成随机数 class App extends React.Component { state = { number: 0 } handleClick = () => { this.setState(() => { return { number: Math.floor(Math.random() * 3) } }) } // 因为两次生成的随机数可能相同,如果相同,此时,不需要重新渲染 // shouldComponentUpdate(nextProps, nextState) { // console.log('最新状态:', nextState, ', 当前状态:', this.state) // return nextState.number !== this.state.number // } render() { // console.log('render') return ( <div> <NumberBox number={this.state.number} /> <button onClick={this.handleClick}>重新生成</button> </div> ) } } class NumberBox extends React.Component { shouldComponentUpdate(nextProps) { console.log('最新props:', nextProps, ', 当前props:', this.props) // 如果前后两次的number值相同,就返回false,不更新组件 return nextProps.number !== this.props.number // if (nextProps.number === this.props.number) { // return false // } // return true } render() { console.log('子组件中的render') return <h1>随机数:{this.props.number}</h1> } } ReactDOM.render(<App />, document.getElementById('root')) -
纯组件
纯组件:PureComponent 与 React.Component 形似
区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较
原理:纯组件内部通过分别对比 前后两次的 props 和 state 的值,来决定是否重新渲染组件
class Hello extends React.PureComponent{ render(){ return ( <div>纯组件</div> ) } }来说说这个比较:
纯组件内部的对比是 shallow compare(浅层对比)
对于值的类型来说,这个比较就是在比较两个值是否相同,这样是没有坑的。
let number = 0 let newNumber = number newNumber = 2 console.log(number === newNumber) // false state = { number: 0 } setState({ number: Math.floor(Math.random() * 3) }) // PureComponent内部对比:最新的state.number === 上一次的state.number // false,重新渲染组件对于引用类型来说:只比较对象的引用(地址)是否相同
const obj = { number: 0 } const newObj = obj newObj.number = 2 console.log(newObj === obj) // true state = { obj: { number: 0 } } // 错误做法 state.obj.number = 2 setState({ obj: state.obj }) // PureComponent内部比较:最新的state.obj === 上一次的state.obj // true,不重新渲染组件例子:
import React from 'react' import ReactDOM from 'react-dom' /* 组件性能优化: */ // 引用类型: const obj = { number: 0 } const newObj = obj newObj.number = 2 console.log(newObj === obj) // true // 生成随机数 class App extends React.PureComponent { state = { obj: { number: 0 } } handleClick = () => { // 正确做法:创建新对象 const newObj = { ...this.state.obj, number: Math.floor(Math.random() * 3) } this.setState(() => { return { obj: newObj } }) // 错误演示:直接修改原始对象中属性的值 /* const newObj = this.state.obj newObj.number = Math.floor(Math.random() * 3) this.setState(() => { return { obj: newObj } }) */ } render() { console.log('父组件重新render') return ( <div> <h1>随机数:{this.state.obj.number}</h1> <button onClick={this.handleClick}>重新生成</button> </div> ) } } ReactDOM.render(<App />, document.getElementById('root'))// 数组 // 正确!创建新数据 const newObj = {...state.obj, number: 2} setState({ obj: newObj }) // 正确!创建新数据 // 不要用数组的push / unshift 等直接修改当前数组的的方法 // 而应该用 concat 或 slice 等这些返回新数组的方法 this.setState({ list: [...this.state.list, {新数据}] })注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!
5. 虚拟 DOM 和 Diff 算法
组件中只有一个 DOM 元素需要更新的时候,也要把整个组件的内容都重新渲染嘛?
答案显然是不是的。我们想要的就是,部分更新,只更新变化的地方
虚拟 DOM:本质上就是一个 JS 对象。就是在描述。我们要写的内容(UI)

这个过程是怎么执行的呢?
-
初次渲染时候,React 会根据初始 state(Model),创建一个虚拟 DOM 对象(树)
-
根据虚拟 DOM 生成真正的 DOM,渲染到页面中
-
当数据变化后(setState(),重新根据新的数据,创建新的虚拟 DOM 对象(树)。
-
与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
-
最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0GZxMb1u-1666060510366)(C:/Users/20946/AppData/Roaming/Typora/typora-user-images/image-20221013121653467.png)]
6. React 路由基础
-
React 路由介绍
前端应用大多都是 SPA(单页面应用程序),也就是只有一个 HTML 页面的应用程序。因为它的用户体验更好、对服务器的压力更小。为了有效的使用单个页面来管理更多页面的功能,前端路由应然而生。
- 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
- 前端路由是一套映射规则,在 React 中,是 URL 路径与组件的对应关系
- 使用 React 路由简单来说,就是配置路径和组件(配对)
-
路由的基本使用
使用步骤
-
安装:npm i react-router-dom
-
导入路由的三个核心组件:Router / Route / Link
import { BrowserRouter as Router, Route, Link } from 'react-router-dom' -
使用 Router 组件包裹整个应用(重要)
<Router> <div className="App"> // … 省略页面内容 </div> </Router> -
使用 Link 组件作为导航栏菜单(路由入口)
<Link to="/first">页面一</Link> -
使用 Route 组件配置路由规则和要展示的组件(路由出口)
const First = () => <p>页面一的页面内容</p> <Router> <div className="App"> <Link to="/first">页面一</Link> <Route path="/first" component={First}></Route> </div> </Router>
Router 组件:只需要使用一次
两种常用的 Router :HashRouter 和 BrowseRouter
HashRouter:使用 URL 的哈希值实现(localhost:3000/#/first)
(推荐)BrowseRouter:使用 H5 的 history API 实现(localhost:3000/first)
Link组件:用于指定导航链接
// to属性:浏览器地址栏中的pathname(location.pathname) <Link to="/first">页面一</Link>Route 组件:指定路由展示组件相关信息
// path属性:路由规则 // component属性:展示的组件 // Route组件写在哪,渲染出来的组件就展示在哪 <Route path="/first" component={First}></Route> -
-
路由的执行过程
-
点击 Link 组件(a标签),修改了浏览器地址栏中的 url。
-
React 路由监听到地址栏 url 的变化。
-
React 路由内部遍历所有 Route 组件,使用路由规则(path )与 pathname 进行匹配。
-
当路由规则(path)能够匹配地址栏中的 pathname时,就展示该 Route 组件的内容。
-
-
编程式导航
场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?
编程式导航:通过 JS 代码来实现页面跳转
history 是 React 路由提供的,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数 path 表示要跳转的路径
go(n): 前进或后退到某个页面,参数 n 表示前进或后退页面数量(比如:-1 表示后退到上一页)
class Login extends Component { handleLogin = () => { // ... this.props.history.push('/home') } render() {...省略其他代码} } -
默认路由
问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?
默认路由:表示进入页面时就会匹配的路由
默认路由path为:/
<Route path="/" component={Home} /> -
匹配模式
-
模糊匹配模式
问题:当 Link组件的 to 属性值为 “/login”时,为什么 默认路由(/) 也被匹配成功?
默认情况下,React 路由是模糊匹配模式
模糊匹配规则:只要 pathname 以 path 开头就会匹配成功

-
精确匹配
问题:默认路由任何情况下都会展示,如何避免这种问题?
给 Route 组件添加 exact 属性,让其变为精确匹配模式
精确匹配:只有当 path 和 pathname 完全匹配时才会展示该路由

推荐:给默认路由添加 exact 属性。
-

3706

被折叠的 条评论
为什么被折叠?



