一、React起步
1.1 使用React脚手架初始化项目
// 1. 初始化项目
npx create-react-app 项目名
// 2. 启动项目
npm start
1.2 React项目结构介绍
public/ 公共资源
index.html 首页html文件(必要)
manifest.json PWA应用元数据
robots.txt 爬虫规则文件
src/ 项目功能代码
index.js 项目入口文件(必要)
App.js 项目的根组件
App.test.js App组件的测试文件
reportWebVitals.js 页面性能监测
setupTests.js 组件测试
1.3 Html与JSX
Vue推荐使用template模板,React推荐使用JSX语法。
JSX是JavaScript XML的简写,表示JavaScript代码中写的XML(HTML)格式的代码
// JSX写法
<div className="App">
<h1>Users List</h1>
<ul>
<li>张三</li>
<li>李四</li>
</ul>
</div>
使用步骤
- 使用JSX语法创建react元素
const title = <h1>Users List</h1>
- 使用React.DOM.render()方法渲染react元素到页面中
React.DOM.render(title, root)
JSX在脚手架中
1.4 绑定Class和Style
(1) Vue
- Vue中使用
class绑定Class,使用style绑定Style; - 当使用变量绑定时,可以使用v-bind:class的简写
:class以及v-bind:style的简写:style
- Vue中class和style的值为常量
<div class="title" style="margin: 10px"> hello world </div>
<style>
.title { padding: 5px }
</style>
- Vue中class和style的值为变量
<div :class="{isActive? active:''}" :style="{margin: activeMargin}"> hello world </div>
data:{
activeMargin: "10px",
isActive: true
}
<style>
.active { color: red }
</style>
(2) React
- React中是用
className来绑定 Class,用style来绑定 Style。 - style用
{{}}中括号并传入一个对象,同时该对象需要使用驼峰式命名(如 fontSize)
- React中class和style的值为常量
class HelloWorld extends React.Component {
render(
return (
<div
className="title"
style={{fontSize: '16px'}}
>
hello world
</div>
);
)
}
- React中class和style的值为变量
class HelloWorld extends React.Component {
state:{
isActive: true,
activeStyle: { margin: '10px' }
}
render(
return (
<div
className={`${this.state.isActive ? 'active' : ''}`}
style={activeStyle}
>
hello world
</div>
);
)
}
二、React事件
2.1 React事件处理
2.1.1 事件绑定
(1)在vue中使用v-on的方式绑定事件对象
<button v-on:click="onclick">点我</button>
// 使用v-bind的语法糖@
<button @click="onclick">点我</button>
(2)React
- 语法: on + 事件名称 = { 事件处理程序 },如onClick = { () = {} }
- React事件采用驼峰命名法,比如onChange, onAdd
// 类组件中
class Emit extends React.Component {
handleClick() {
console.log("事件已触发")
}
render() {
return (
<button onClick={ this.handleClick } />
)
}
}
// 函数组件中
function Emit {
function handleClick() {
console.log("事件已触发")
}
return (
<button onClick={ this.handleClick } />
)
}
2.2 事件对象
- 可以通过事件处理程序的参数获取到事件对象
- React中的实际对象叫合成事件(对象)
- 合成事件:兼容所有浏览器
function Emit {
function handleClick(e) {
e.preventDefault()
console.log("事件对象", e)
}
return (
<a onClick={ this.handleClick } >点击但不会跳转</a>
)
}
2.3 类组件和函数组件
- 类组件也称为有状态组件;函数组件也称为无状态组件
- 状态(state) 即数据
- 函数组件没有自己的状态,只负责数据展示(静);
- 类组件有自己的状态,能够更新UI
2.4 事件绑定this指向
2.4.1 箭头函数
class Emit extends React.Component {
handleClick() {
console.log("事件已触发")
}
render() {
return (
<button onClick={ () => this.handleClick() } />
)
}
}
2.4.2 bind()
使用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起
class Emit extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log("事件已触发")
}
render() {
return (
<button onClick={ this.handleClick } ></button>
)
}
}
2.4.3 class的实例方法(常用)
利用箭头函数的形式的class实例方法
class Emit extends React.Component {
handleClick = () => {
console.log("事件已触发")
}
render() {
return (
<button onClick={ this.handleClick } ></button>
)
}
}
三、React组件
3.1 React中组件如何改变数据
使用setState修改组件中state的数据
class ChangeState extends React.Component {
state: {
title: "I am iron man"
}
handleClick = () => {
this.setState(state => ({
title: "I am spiderman"
}))
}
render() {
return (
<button onClick={ this.handleClick } ></button>
)
}
}
在this.setState()中可以传递一个函数或一个对象,建议传递一个函数(state,props) =>{},函数可以接受内部数据state和参数数据props作为参数,而且state和props只读无法修改,每次调用this.setState时读取到的state和Props都是最新,特别适用多次调用this.setState修改同一个state的场景。最后函数返回一个对象,其内容为要修改的state。
3.2 常见的三种组件通讯场景
- 父组件传子组件
- 子组件传父组件
- 兄弟组件间传值
3.2.1 父组件传子组件
- 父组件提供要传递的state数据
- 给子组件标签添加属性,值为state中的数据
- 子组件中通过props接收父组件中传递的数据
// 父组件
class Parent extends React.Component {
state = { firstName: '张三' }
render() {
return (
<div>
数据传递给子组件:<Child name={this.state.lastName} />
<div/>
)
}
}
// 子组件
function Child(props){
return <div> 子组件接收到数据:{props.name} </div>
}
3.2.2 子组件传父组件
- 父组件提供一个回调函数(用于接收数据)
- 将该函数作为属性的值,传递给子组件
- 子组件通过props调用回调函数
- 将子组件的数据作为参数传递给回调函数
// 父组件
class Parent extends React.Component{ state ={ parentMsg : '' }
// 提供回调函数 接收数据
getChildMsg = (msg) =>{
console.log("接受的信息", msg)
}
render(){
return(
子组件:<Child getData={this.getChildMsg} />
)
}
}
// 子组件
class Child extends React.Component{
state = {
childMsg :'子组件信息'
}
handelClick = () =>{
this.props.getChildMsg(this.state.childMsg)
}
render(){
return(
子组件: <button onClick={this.handelClick}>按钮</button>
)
}
}
3.3.3 兄弟组件通讯
- 将 共享状态 提升到最近的公共父组件中,由公共父组件 管理这个状态
- 思想:状态提升
- 公共父组件职责: 1.提供共享状态 2.提供操作共享状态的方法
- 要通讯的子组件只需要通过props接收状态或操作状态的方法
class Parent extends React.Component {
// 父组件提供共享状态
state ={
count: 0
}
// 父组件提供修改状态的方法
countAdd = ()=>{
this.setState({
count: this.state.count+1
})
}
render() {
return (
<div>
<Child1 count={this.state.count}/>
<Child2 Add={this.countAdd}/>
</div>
)
}
}
const Child1 = (props) => {
return <h1>计数器:{props.count}</h1>
}
const Child2 = (props) => {
return <button onClick={()=>{props.countAdd()}}>+1</button>
}
3.2 Context实现跨组件传递数据
(1)vue使用Provide/Inject实现跨组件数据传递
(2)React使用Context实现跨组件数据传递
const ThemeContext = React.createContext();
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
static contextType = ThemeContext;
render() {
return <button>{ this.context }</button>;
}
}
3.3 props进阶
3.3.1 props的children属性
- children属性:表示组件标签的子节点。当组件标签有子节点时,props就会有该属性
const App = props => {
return (
<div>
app组件标签子节点:{this.props.children}
</div>
)
}
<App>我是子节点</App>
3.3.2 props校验
(1)vue实现props校验
// 父组件
<Children :arr="arr"/>
data(){
return {
arr: [0,1]
}
}
// Children组件接收props
props: {
arr: {
type: Array //定义接收数据类型,实现校验功能
require: true // 此项为必传
default: () => [] //设置默认值
}
}
(2)React实现props校验
-
在创建组件的时候指定props的类型、格式,提供当遇到props导致的错误时给出错误提示
-
使用步骤
// 1. 安装包prop-types npm i props-types // 2. 导入prop-types import ProTypes from 'prop-types' function App(props) { return ( <div>{props.arr}</div> ) } // 3. 使用 组件名.propTypes = {}来给组件的props添加 App.propTypes = { // 4. 校验规则通过PropTypes对象来指定 // 约定arr属性为array类型,如果类型不对则会提示错误原因 arr: PropTypes.array } -
props校验约束规则
1. 常见类型:string、number、object、array、func、bool 2. React元素类型:element 3. 必填项:类型.isRequired 4. 特定结构的对象:shape({}) optionalFunc: PropTypes.func, requiredFunc: PropTypes.func.isRequired, shapeObject: PropTypes.shape({ name: PropTypes.string, age: PropTypes.number }) -
props的默认值
function App(props) {
return (
<div>当前页码:{props.page}</div>
)
}
// 设置默认值
App.defaultProps = {
page: 1
}
3.4 组件生命周期
3.4.1 生命周期的三个阶段
(1) 创建时(挂载阶段)
- 执行顺序:constructor -> render -> componentDidMount
| 钩子函数 | 触发时机 | 作用 |
|---|---|---|
| constructor | 创建组件时,最先执行 | 1.初始化state 2.为事件处理绑定this |
| render | 每次组件渲染都会触发 | 渲染UI(但是不能调用setState) |
| componentDidMount | 组件挂载(完成DOM渲染)后 | 1.发送网络请求 2.DOM操作 |
(2) 更新时(更新阶段)
- 执行时机:1.setState() 2. forceUpdate() (主动更新方法) 3. 组件接收到新的props
- 以上三者任意一种变化,组件就会重新渲染
- 执行顺序:render -> componentDidUpdate()
| 钩子函数 | 触发时机 | 作用 |
|---|---|---|
| render | 每次组件渲染都会触发 | 渲染UI(与挂载阶段是同一个render) |
| componentDidUpdate | 组件更新(完成DOM渲染)后 | 1.发送网络请求; 2.DOM操作; ps:如果要setState必须放在if条件中 |
// prevProps=更新前props
class Counter extends React.Component{
componentDidUpdate(prevProps){
// 比较更新前后的props是否相同,来决定是否更新渲染组件
if(prevProps.count !== this.props.count){
this.setState({})
}
}
}
(3) 卸载时(卸载阶段)
- 执行时机:组件从页面中消失
| 钩子函数 | 触发时机 | 作用 |
|---|---|---|
| componentWillUnmount | 组件卸载 | 执行清理工作(比如:清理定时器) |
3.5 组件复用
3.5.1 render props模式
- 创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态 2.操作状态的方法)
- 将Mouse组件的状态作为props.children(state)方法的参数,暴露到组件外部
- 使用props.children()的返回值作为要渲染的数据
// Mouse组件
class Mouse extends React.Component {
// ..省略state和操作方法state的方法
render() {
// 将Mouse组件的状态作为props.children(state)方法的参数,暴露到组件外部
return this.props.children(this.state);
}
}
// 父组件使用Mouse组件
<Mouse>{ showMouse }</Mouse>
// showMouse方法
const showMouse = (mouse) => {
// 使用props.children()的返回值作为要渲染的数据
return (
<p>
鼠标的位置:{mouse.x},{mouse.y}
</p>
);
};
3.5.2 高阶组件
- 创建一个函数,名称约定以with开头
- 指定函数参数,参数应该以大写字母开头(作为要渲染都而组件)
// 1. 创建高阶组件
const withMouse = (WrappedComponent) => {
class Mouse extends React.Component {
// ..省略state和操作方法state的方法
render() {
// 2. 返回带有组件数据的传入组件(初始组件)
// {...this.props}将初始组件的props一并返回
return <WrappedComponent {...this.state} {...this.props}></WrappedComponent>;
}
}
// 设置displayName,便于调试时区分不同的组件
Mouse.display = `WithMouse${WrappedComponent.displayName}`
return Mouse
}
// 初始组件
const ShowMouse = (mouse) => {
return (
<p>
鼠标的位置:{mouse.x},{mouse.y}
</p>
);
};
// 3. 将初始组件作为参数传入高阶组件,返回升级后的组件
const MousePosition = withMouse(ShowMouse);
// 4. 使用经高阶组件升级后的组件
<MousePosition></MousePosition>
四、React原理
4.1 setState()进阶
setState()
4.1.1 异步的setState()
- setState()是异步更新数据的
- 提示:使用该语法时,后面的setState()不要依赖前面的setState()
- 可以多次调用setState(), 只会触发一次重新渲染
4.1.2 推荐setState()语法
使用setState((state, props) => {})语法,可以改变多次调用setState()而只渲染一次的情况。
state = {
count: 0
}
// state表示最新的state,props表示最新的props
this.setState((state,props) => {
return {
count: state.count + 1
}
})
4.1.3 setState()的第二个参数
- 语法:setState(updater, [callback])
- 适用场景:在状态更新后立即执行某个操作
this.setState(
(state, props) => {},
() => {console.log('在状态更新后立即执行这个操作')}
)
4.2 组件性能优化
4.2.1 减轻state
- state只储存组件渲染相关的数据
- 不做渲染的数据不要放在state中
- 对于多个方法中用到的数据,应该放在this中
4.2.2 避免不必要的渲染
- 使用shouldComponentUpdate(nextPorps, nextState),解决父组件更新引起的子组件更新问题
- 通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染
- 触发时机:更新阶段的钩子函数,组件重新渲染前执行
class Hello extends React.Component {
// newProps:最新的props,newState:最新的state
shouldComponentUpdate(newProps, newState){
// 根据条件,决定是否重新渲染
// 如果最新的xxx与旧xxx不同则重新渲染,反之则不渲染
return newState.xxx !== this.state.xxx
}
render() {...}
}
4.2.3 纯组件
2.3.1 纯组件使用
- 纯组件:PureComponent与React.Component功能相似
- 区别:PureComponent内部已集成shouldComponentUpdate钩子实现
- 原理:纯组件内部通过分别对比前后两次props和state的值,来决定是否重新渲染组件
class Hello extends React.PureComponent {
render() {...}
}
2.3.2 纯组件注意点
- 纯组件内部对比的是shallow compare(浅层对比)
- 对于引用类型来说:只比较对象的引用(地址)是否相同,若仅仅只是修改对象中的值,那么纯组件将不会捕获到引用对象的更新,所以导致纯组件不会重新渲染
- 当state或props中的属性值为引用类型时,应创建数据,不应直接修改原数据
// 纯组件中引用类型更新正确做法
const newObj = {...this.state.obj, number: 3}
this.setState({obj:newObj})
4.3 虚拟DOM和Diff算法
虚拟DOM
虚拟DOM本质是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)
执行过程
- 初次渲染时,React 会根据初始state(Model),创建一个虚拟DOM对象(树)。
- 根据虚拟DOM生成真正的DOM,渲染到页面中。
- 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
- 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容。
- 最终,React 只将变化的内容更新(patch)到DOM 中,重新渲染到页面。
五、React路由
5.1 React路由介绍
5.1.1 路由的使用
// 1. 安装(此处写法基于6+)
npm i react-router-dom@6
// 2. 导入路由核心组件:Router/Route/Link
import { BrowserRouter as Router, Routes, Route,Link } from 'react-router-dom'
<Router>
<div className='App'>
// 3.使用Router组件包裹整个应用
<Link to="/hello">Hello页面</Link>
// 4. 使用Route组件配置路由规则和要展示的组件(路由出口)
// react-router-dom v6版本Route外层需要Routes包裹
<Routes>
<Route path="/hello" element={<Hello />} />
</Routes>
</div>
</Router>
5.2 常用组件说明
- Router组件:包裹整个应用,一个 React 应用只需要使用一次
- 两种常用 Router:HashRouter 和 BrowserRouter
- HashRouter:使用URL的哈希值实现(localhost:3000/#/first)(推荐)
- BrowserRouter:使用H5的 history API 实现(localhost:3000/first)
- Link组件:用于指定导航链接(a标签)
// to属性:浏览器地址栏中的pathname(location.pathname)
<Link to="/hello">Hello页面</Link>
- Route组件:指定路由展示组件相关信息
// path属性:路由规则
// component属性: 展示的组件
// Route组件写在哪,渲染出来的组件就展示在哪
<Routes>
<Route path="/hello" element={<Hello />} />
</Routes>
5.3 路由执行过程
- 点击Link组件(a标签),修改了浏览器地址栏中的url。
- React 路由监听到地址栏 url的变化。
- React路由内部遍历所有 Route 组件,使用路由规则( path)与 pathname 进行匹配。
- 当路由规则(path )能够匹配地址栏中的pathname 时,就展示该 Route 组件的内容。