React基础
React理论基础——基于函数组件
一、修改状态
1. useState
作用:1. 修改state;2. 更新UI
思想:数据驱动视图
建议:从JSX抽离逻辑代码,注意类组件的this指向
二、表单处理
1. 受控组件
state = {txt: ''};
<input type='text' value={this.state.txt} onChange={e => this.setState({txt: e.target.value})} />;
state = {isChecked: false};
<input type='checkbox' checked={this.state.isChecked} onChange={e => this.useState({isChecked: e.target.checked})} />
2. 多表单元素优化
步骤:
- 给表单元素添加
name属性,名称与state相同- 根据表单元素类型获取对应值
- 在
change事件处理程序中通过[name]来修改对应的state
const handleForm = (e) => {
const target = e.target;
// 根据表单元素类型获取值
const value = target.type === 'checkbox' ? target.checked : target.value;
// 根据name设置对应的state
this.setState({[target.name]: value})
}
<input type='text' name='txt' value={this.state.txt} onChange={this.handleForm.bind(this, e)} />
3. 非受控组件
说明:借助于
ref,使用原生DOM方式来获取表单元素的值使用步骤:
- 调用
React.createRef()创建一个ref对象- 将创建好的
ref对象添加到文本框中- 通过
ref获取文本框的值
txtRef = React.createRef();
<input type='text' ref={this.txtRef} />;
console.log(this.txtRef.current.value);
三、组件间通讯
传递数据:给组件传递属性
接收数据:
props
props的特点:
- 可以给组件传递任意类型的数据;
props是只读属性,不能修改
1. 父到子
a. 父组件提供要传递的state数据
b. 父组件给子组件传递属性,值为state中的数据
c. 子组件通过props接受通过属性传过来的值
2. 子到父
思路:利用毁掉函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
a. 父组件提供一个回调函数(用于接收数据)
b. 将该函数作为属性值传递给子组件,子组件传入实参(子组件需要传递的数据)调用该函数
3. 兄弟间
思想:状态提升
将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
公共父组件的职责:1. 提供共享状态;2. 提供操作共享状态的方法
要通讯的子组件只需要通过
props接收状态和操作状态的方法
4. Context跨组件传递数据
使用步骤:
-
调用
React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件cont {Provider, Consumer} = React.createContext(); -
使用
Provider组件作为父节点<Provider> <div className='App'> <Child /> </div> </Provider> -
设置
value属性,表示要传递的数据<Provider value='pink' /> -
调用
Consumer组件接收数据<Consumer> {data => <span>data参数表示接收到的数据 -- {data}</span>} </Consumer>
四、props深入
1. children属性
children属性表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
children属性与普通的props一样,值可以是任意值(文本、React元素、组件或者函数等)
2. props校验
对于组件来说,
props是外来的,无法保证组件使用者传入什么格式的数据,如果传入的数据格式不对,可能会导致组件内部报错。关键问题:组件的使用者不知道明确的错误原因
props校验:允许在创建组件的时候,就指定props的类型、格式等
作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性
使用步骤:
-
安装包
prop-types (yarn add prop-types / npm i prop-types) -
导入
prop-types包 -
使用
组件名.propTypes = {}来给组件的props添加校验规则 -
校验规则通过
PropTypes对象来指定 -
校验规则通过
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 -
必填项:
isRequiredPropTypes.func.isRequired -
特定结构的对象:
shape({ })PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number })
3. props的默认值
给props设置默认值,未传入props时生效
场景:显示分页下每页的条数
const App = props => {
return (
<div>
<h1>此处展示props的默认值:{props.pageSize}</h1>
</div>
)
}
App.defaultProps = {
pageSize = 10
}
五、组件的生命周期
1. 生命周期的三个阶段(针对class组件)
创建时(挂载阶段)
-
执行时机:组件创建时(页面加载时)
-
执行顺序:
constructor() --> render() --> componentDidMount()钩子函数 触发时机 作用 constructor创建组件时,最先触发 1. 初始化 state
2. 为事件处理程序绑定thisrender每次组件渲染都会触发 渲染 UI(注意:不能调用setState(),会导致递归渲染报错)componentDidMount组件挂载(完成 DOM渲染)后1. 发送网络请求
2.DOM操作
更新时(更新阶段)
-
执行顺序:
render()-->componentDidUpdate()钩子函数 触发时机 作用 render每次组件渲染都会触发 渲染 UI(与挂载阶段是同一个render)componentDidUpdate(更新前的值)组件更新(完成 DOM渲染)后1. 发送网络请求
2.DOM操作
注意:如果要setState()必须放在一个if条件中注意:
setState会导致render,出现递归更新,故需要放在if条件中if (比较更新前后的props是否相同) {执行逻辑} -
导致组件重新渲染的三种情况:
1. setState() 2. forceUpdate() 3. 组件接收到新的props
卸载时(卸载阶段)
- 执行时机:组件从页面消失
componentWillUnmount,组件卸载(从页面消失时)触发,执行清理工作(比如:清理定时器等)
六、React组件复用
1. render props模式
思路分析:将要复用的
state和操作state的方法封装到一个组件中通过向复用组件传递一个函数实现,形参用来放
state中的变量,返回值为需要渲染的UI只实现了状态逻辑代码的复用,没有实现
UI结构的复用
例如:创建一个复用鼠标位置的组件(复用子组件,相当于通过子给父传递数据)
function Son(props) {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const handleMouseover = e => {
setX(e.clientX);
setY(e.clientY);
};
useEffect(() => {
window.addEventListener('mousemove', handleMouseover);
return () => {
// 组件卸载时移除事件监听对象
window.removeEventListener('mousemove', handleMouseover);
};
}, []);
return <div>{props.children({ x, y })}</div>;
}
import propTypes from 'prop-types';
// 添加props校验
Son.propTypes = {
children: PropTypes.func.isRequireds
}
function Father() {
return (
<Son>
{mouse => (
<p>
当前鼠标的位置为:x-{mouse.x}, y-{mouse.y}
</p>
)}
</Son>
);
}
2. 高阶组件(HOC)
目的:实现状态逻辑复用
采用包装(装饰)模式,通过包装组件,增强组件功能
思路:
Higher-Order Component是一个函数,名称约定以with开头,接收要包装的组件,返回增强后的组件高阶组件内部创建一个组件,在这个组件中提供复用的逻辑代码,通过
prop将复用的状态传递给被包装组件WrappedComponent
const EnhancedComponent = withHOC(WrappedComponent)
使用步骤:
- 创建一个函数,名称约定以
with开头 - 指定函数形参,参数应该以大写字母开头
- 在函数内部创建一个组件,提供复用的状态逻辑代码,并返回
- 在该组件中,渲染参数组件,同时将状态通过
porps传递给参数组件
import React, { useState, useEffect } from 'react'
import reactDOM from 'react-dom'
/* 创建高阶组件 */
const withMouse = WrappedComponent => {
const Mouse = props => {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
const handleMouseMove = e => {
setX(e.clientX)
setY(e.clientY)
}
useEffect(() => {
window.addEventListener('mousemove', handleMouseMove)
return () => {
window.removeEventListener('mousemove', handleMouseMove)
}
})
return <WrappedComponent {...{ x, y }} {...props} />
}
/* 给被高阶组件渲染出来的组件命名displayname */
const getDisplayName = WrappedComponent => {
return WrappedComponent.displayName || WrappedComponent.name || 'component'
}
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
return Mouse
}
/* 使用高阶组件 */
const Position = props => {
const { x, y, a } = props
return (
<div>
<h3>a={a}</h3>
<p>
当前鼠标的位置为 x: {x}, y: {y}
</p>
</div>
)
}
const MousePosition = withMouse(Position)
reactDOM.render(<MousePosition a="1" />, document.getElementById('root'))
问题:使用高阶组件后会得到名称相同的组件,比如本例中,通过高阶组件渲染出来的都为<Mouse />
原因:默认情况下,React使用组件名称作为displayName
解决:设置displayName,便于调试时区分不同的组件,用于设置调试信息(React Developer Tools信息)
const getDisplayName = WrappedComponent => {
return WrappedComponent.displayName || WrappedComponent.name || 'component'
}
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
问题:props丢失
原因:高阶组件没有继续往下传递props
解决:渲染WrappedComponent时,将props也一起传给被包裹的组件
七、React原理
1. setState的说明
setState是异步更新数据的注意:使用该语法时,后面的
setState()不要依赖于前面的setState(),因为两次拿到的值都是变化前的值每个操作可以多次调用
setState,只会触发一次页面重新渲染
错误示范:
const [count, setCount] = useState(1)
const handleClick = () = > {
setCount(count + 1) // 异步完成之后count变为2
console.log(count) // 1
setCount(count + 1) // 异步完成之后count变为2
}
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。setState(state => {})
const [count, setCount] = useState(1)
const handleClick = () = > {
setCount(count => {
return count + 1
})
setCount(count => {
console.log('拿到最新的count: ', count) // 2
return count + 1
})
console.log('count: ', count)
}
2. JSX语法的转化过程
JSX仅仅是createElement()方法的语法糖
JSX语法被@babel/preset-react插件编译为createElement()方法
React元素:是一个对象,用来描述希望在屏幕上看到的内容
const element = <h1 className="greeting">Hello JSX!</h1>
// 等价于
const element = React.createElement('h1', {className: 'greeting'}, 'Hello JSX!')
3. 组件更新机制
setState()的两个作用:1. 修改state,2. 更新组件(UI)过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件及其所有子组件,函数组件
useEffetct在不含依赖的情况下执行顺序为子到父)
4. 组件性能优化
- 减轻
state:只存储跟组件渲染相关的数据 - 避免不必要的重新渲染,父组件更新会引起子组件更新(导致子组件没有变化时也会重新渲染),函数组件
useEffect添加依赖可以减少不必要的渲染
5. 虚拟DOM和Diff算法
React更新视图的思想是:只要state变化就重新渲染视图虚拟
DOM配合Diff算法实现部分更新
虚拟DOM:本质上就是一个JS对象,用来描述希望在页面上看到的内容
执行过程:
- 初次渲染时,
React会根据初试state(Model)(状态和JSX),创建一个虚拟DOM对象(树) - 根据虚拟
DOM生成真正的DOM,渲染到页面中 - 当数据变化后,重新根据新的数据,创建新的虚拟
DOM对象(树) - 与上一次得到的虚拟
DOM对象,使用Diff算法对比,得到需要更新的内容 - 最终,
React只将变化的内容更新(patch)到DOM中,重新渲染到页面
八、React路由基础
1. React路由介绍
SPA单页面应用,带来的用户体验更好,对服务器压力更小,为了有效的使用单个页面来管理原来多页面的功能,前端路由应运而生。
- 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
- 前端路由是一套映射规则,在
React中,是URL路径与组件的对应关系 - 使用
React路由简单来说,就是配置路径和组件(配对)
2. 路由的基本使用 (6.x)
使用步骤:react-router-dom@6.0.2
-
安装:
yarn add react-router-dom -
导入路由的三个核心组件:
Router/Route/Linkimport {BrowserRouter as Router, Route, Link} from 'react-router-dom' -
使用
Router组件包裹整个应用 -
使用
Link组件作为导航菜单(入口) -
使用
Route组件配置路由规则和要展示的组件(出口)
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'
const First = () => <p>第一个页面的内容</p>
const App = () => {
return (
<Router>
<div>
<h1>React路由基础</h1>
{/* 路由入口 */}
<Link to="/first">页面一</Link>
{/* 路由出口 */}
<Routes>
<Route path="/first" element={<First />} />
</Routes>
</div>
</Router>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
3. 路由的执行过程
- 点击
Link组件(a标签),修改了浏览器地址栏中的URL React路由监听到地址栏URL的变化React路由内部遍历所有Route组件,使用路由规则(path)与element进行匹配- 当路由规则(
path)能够匹配地址栏中的pathname时,就展示该Route组件的内容
4. 编程式导航
props.history.push('/')编程式导航:通过
JS代码来实现页面跳转
history是React路由提供的,用于获取浏览器历史记录的相关信息
push(path):跳转到某个页面,参数path表示要跳转的路径
go(n):前进或者后退到某个页面,参数n表示前进或后退页面的数量(比如:-1表示后退到上一页)
5. 默认路由
默认路由:表示进入页面就会匹配的路由,即
<Route path="/" element={<Home />} />
6. 匹配模式
模糊匹配模式(默认):只要pathname以path开头就会被匹配,比如/login/a可以匹配出/ /log 等
精确匹配:给Route添加exact属性即可

浙公网安备 33010602011771号