当前位置: 首页 > news >正文

React学习 ---- 基础知识学习

文章目录

  • 1. 初识React
    • 1.1 组件化
      • 1.1
      • 1.1.2 事件绑定中的this
    • 1.2 JSX语法
      • 1.2.1 认识JSX及其基本使用
        • 1.2.1.1 认识JSX
        • 1.2.1.2 JSX基本书写规范
      • 1.2.2 JSX事件绑定
      • 1.2.3 条件渲染
      • 1.2.4 列表渲染
    • 1.3 JSX本质和原理
    • 1.4 React脚手架
  • 2. React语法
    • 2.1 组件化开发
      • 2.1.1 类组件
      • 2.1.2 函数式组件
    • 2.2 生命周期
    • 2.3 组件间通信
      • 2.3.1 父传子
      • 2.3.2 子传父
    • 2.4 组件插槽
    • 2.5 非父子通信
      • 2.5.1 context
      • 2.5.2 事件总线
    • 2.6 setState使用详解
      • 2.6.1 为什么使用setState?
      • 2.6.2 setState的详细使用

1. 初识React

1.1 组件化

1.1

1.1.2 事件绑定中的this

在类中定义一个函数,将它绑定在 button 上,那么当前函数的 this 指向谁呢?

class App extends React.component {constructor() {this.state = {message: 'hello world'}// 对需要绑定的方法,提前绑定好thisthis.btnClick = this.btnClick.bind(this)}// methodsbtnClick() {this.setState({message: 'hello react'})}// renderrender() {return (<div><h1>{this.state.message}</h1><button onClick={this.btnClick}>修改message</button></div>)}}
  • 默认情况下,this 的指向是undefined
    • 在button上绑定的事件实际上是将btnClick代码块直接给了onclick,执行的时候相当于
    • btnClick( )
    • // 此时也就是this绑定规则中的默认绑定,在非严格模式下应该指向window,但是我们使用babel对代码解析,也就是在严格模式下,此时的this指向undefined
  • 那么我们就需要给btnClick绑定this,下面是两种方法:
      1. < button onClick={this.btnClick.bind(this)} > 修改message</ button>
      1. 在constructor中,this.btnClick = this.btnClick.bind(this)
    • 更推荐第二种,因为若有多个button,那么每个button都需要处理this的重定向,会造成代码冗余

1.2 JSX语法

1.2.1 认识JSX及其基本使用

1.2.1.1 认识JSX
const element = <h1>{message}</h1>

这段代码其实就是一段JSX的语法。

JSX语法:

  • JSX是JavaScript的语法扩展,也叫做javascript XML,因为看起来像是XML代码;
  • 它用来描述UI界面,而且可以和javascript融合起来使用;
  • 它不同于Vue的模版语法,不需要专门学习模版语法中的一些指令(v-bind、v-for、v-model…)

为什么React选择了JSX?

  • React认为渲染逻辑UI界面存在内在耦合
    • UI组件需要绑定事件
    • UI组件需要展示数据状态
    • 数据改变后又需要改变UI

他们之间是密不可分的,所以React没有将标记分离到不同的文件中,而是将他们组合在了一起,这个地方就叫做组件。

1.2.1.2 JSX基本书写规范
  1. 顶层只能有一个根元素,所以一般我们使用< div >包一下
  2. 为了方便阅读,通常会包裹一个( ) ,将JSX当做一个整体看待,而且这样也方便换行
  3. 其中可以有双标签,也可以有单标签(例:< h1 ></ h1>、< App />)
render(){return (<div></div>)
}
  1. JSX的注释写法
render(){return (<div>{ /* 注释 */ }</div>)
}
  1. JSX嵌入变量作为子元素
    • 变量是NumberStringArray类型时可以直接显示;
    • 变量时nullundefinedBoolean时,内容为空(若需要展示出来需要转化成字符串形式)
    • Object类型不能作为子元素
  2. 插入表达式或三元运算符或执行函数
render() {const { message, count, array, a, b, c, obj, movies } = this.statereturn (<div>{message + "+" + count}  // 运算表达式{count === 0 ? 'true' : 'false'} // 三元运算符<ul>{movies.map(m => <h1>{m}</h1>)}</ul> // 函数</div>)}
  1. JSX绑定属性

    • titleclassidsrc内联样式绑定
    • 基本属性绑定
    render() {const src = "https://www.baidu.com"return (<a href={src}></a>)}
    }
    
    • class样式绑定
    rende() {const isActive = falseconst classNames = ['a', 'b']if (isActive) classNames.push('c')return (<div><h1 className={`a b ${isActive ? 'ab' : 'abc'}`}></h1><h1 className={classNames.join(" ")}></h1></div>)}
    }
    
    • style样式绑定
      • 样式的绑定需要以对象的形式进行 绑定
    render() {return (<div><h1 style={{ color: "red", fontSize: "20px" }}>哈哈哈哈哈</h1></div>)}
    }
    

1.2.2 JSX事件绑定

this的绑定问题

  1. 上面我们说了使用bind()来改变this的指向,这里不再多说;
  2. ES6中Class出现了class fields ,所以我们可以使用箭头函数来改变this的指向(此时的this将相外层寻找,最终找到组件实例对象,这种方法不常用,了解即可)
class App extends React.Component{...btnClick = ()=>{console.log('btnClick',this) }render() {return (<div><button onClick={this.btnClick}>btnClick</button></div>)}}
}
  1. 第三种方法是直接传入一个箭头函数(常用!!!)
    这里的this,相当于是显式绑定this向外寻找,最终找到组件实例对象。
class App extends React.Component{...btnClick (){console.log('btnClick',this) }render() {return (<div><button onClick={()=>this.btnClick()}>btnClick</button></div>)}}
}

为什么这么推荐第三种方式呢?

当我们遇到event参数和其他参数的传递时,前面两种事件的绑定方式并不能很好的传递。

现有两个按钮,第一个按照bind()传递参数,第二种按照箭头函数传递。

 class App extends React.Component {...btnClick(event, name, age) {console.log('event', event);console.log('name', name);console.log('age', age);console.log('this', this);}render() {const { message } = this.stateconst { name, age } = {name: 'why',age: 18}return (<div><button onClick={this.btnClick.bind(this, name, age)}>按钮1</button><button onClick={(event) => this.btnClick(event, name, age)}>按钮2</button></div>)}}
  1. 使用bind( )传递
    在这里插入图片描述
  2. 使用箭头函数传递
    在这里插入图片描述

显然,第一种传递方式出现参数不对应的情况,第二种参数严格对应,所以我们更推荐第二项种传递方法

1.2.3 条件渲染

条件渲染的常用三种方式

  1. 根据变量值决定渲染内容
render() {const { isShow } = this.stateconst showEl = null;// 1. 根据变量值返回需要的elementif (isShow) {showEl = <h1>条件渲染1{isShow}</h1>} else {showEl = <h1>条件渲染2{isShow}</h1>}return (<div>{showEl}</div>)}
  1. 三元运算符
render() {const { isShow } = this.stateconst showEl = null;return (<div><div>{isShow ? <h1>三元运算符1</h1> : <h2>三元运算符2</h2>}</div></div>)}
  1. &&逻辑与运算
render() {const { isShow } = this.stateconst showEl = null;return (<div><h1>{person && <div>{person.name + "" + person.age}</div>}</h1></div>)}

v-show 的效果我们也能实现。

{/* v-show效果实现 */}
<div style={{ display: isShow ? 'block' : 'none' }}>{person.name}</div>

1.2.4 列表渲染

react中,对于列表渲染,我们最常用的就是map高阶函数,对数据进行处理时我们会使用filterslice等函数。

列表渲染中的key也很重要,我们需要key作为唯一标识,主要是为了提高diff算法的效率。

class App extends React.Component {constructor() {super()this.state = {students: [{ id: 1, name: '蒋敦豪', age: 29 },{ id: 2, name: '鹭卓', age: 28 },{ id: 3, name: '李耕耘', age: 27 },{ id: 4, name: '李昊', age: 26 },{ id: 5, name: '赵一博', age: 25 },{ id: 6, name: '张钥沅', age: 24 },{ id: 7, name: '赵小童', age: 23 },{ id: 8, name: '何浩楠', age: 22 },{ id: 9, name: '陈少熙', age: 22 },{ id: 10, name: '王一珩', age: 20 },]}}render() {const { students } = this.statereturn (<div><h1>学生列表</h1><div>{students.map(item => {return (<div key={item.id}>{'排行:' + item.id + "姓名:" + item.name + " 年龄:" + item.age}</div>)})}</div></div>)}}

1.3 JSX本质和原理

实际上,JSX 语法是React.createElement(component, props, ...children)的语法糖。

  • 所有的JSX都将通过React.createElement()被转换。

三个参数:

  • component – ReactElement的类型
    • 可以是普通标签<div>
    • 也可以是组件标签<App>;
  • props – 标签属性
    • classtitle自定义属性
    • 都以对象的形式传递
  • children – 子标签
    • 以数组的形式传递

在这里插入图片描述
这里我们可以知道babel解析后有了react的虚拟dom之后才是生成浏览器dom节点。

插一嘴:虚拟DOM的作用是什么?

  1. 更新数据
    首先是在更新数据时减少浏览器渲染压力。
    当页面中数据改变后会触发render(),重新渲染虚拟DOM,而diff算法会将新旧DOM做对比,从而有选择的重新渲染更改的DOM;

  2. 跨端渲染组件/控件
    React中的虚拟DOM若在web端显示,则会渲染成对应的divbutton等组件;若在ios或者android显示,那么就会渲染成对应的uiViewUIButton控件。很好的做到了跨端开发。

  3. 命令式变成—>声明式编程

    • 你只需要告诉React希望让UI是什么状态;
    • React来确保DOM和这些状态是匹配的;
    • 你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来;

现在来做一个图书总价的小demo,
在这里插入图片描述

其中我们在增加或减少某类图书的购买数量时是这样做的:
因为React中修改数据不能直接修改,所以需要先做浅拷贝,再进行赋值

crease(index) {const newBooks = [...this.state.books]newBooks[index].count++this.setState({books: newBooks})}
// 减少图书数量也同理

1.4 React脚手架

脚手架都是基于node环境,在使用脚手架前需先搭建node环境;

安装react脚手架

npm install create-react-app -g

创建react项目

craete-react-app [项目名称]

在这里插入图片描述

了解react初始文件:
在这里插入图片描述

2. React语法

2.1 组件化开发

组件化开发时React的三大特点之一,当然,其他的框架也有使用到组件化思想,但是React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分为很多类组件:

  • 根据组件定义
    • 函数组件类组件
  • 根据组件内部是否有需要维护的状态
    • 无状态组件有状态组件
    • 一般来说,类组件是有状态组件,而函数组件时无状态组件
  • 根据不同职责
    • 展示型组件容器型组件

2.1.1 类组件

在这里插入图片描述


render()return 返回值
在这里插入图片描述

2.1.2 函数式组件

函数式组件使用function进行定义,其返回值与类组件返回值一致。

函数式组件特点:

  1. 没有生命周期,也会被挂载或更新,但是没有生命周期;
  2. this关键字不能指向组件实例(因为没有组件实例);
  3. 没有内部状态(state)

2.2 生命周期

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2.3 组件间通信

2.3.1 父传子

首先接触到的是父传子,跟vuev-bind类似,我们将参数放在子组件身上,通过props接受。

<MainProjectList title="推荐列表" />
import axios from 'axios'
import { Component } from 'react'export class MainProjectList extends Component {constructor(props) {super(props)this.state = {projectList: []}}render() {const { projectList } = this.statereturn (<div className="main-project-list"><h2>{this.props.title}</h2></div>)}
}export default MainProjectList

在这里插入图片描述
在之前使用vue开发时,我们一般使用TypeScript进行类型验证;在React中,我们可以使用FlowTypeScript进行类型验证,但最常用的还是proptypes这个库。

// 这里代码提示可能不太灵敏,需要自己手动修改一下
MainProjectList.propTypes = {title: PropTypes.string.isRequired
}

默认值:

MainProjectList.defaultProps = {title: '推荐列表'
}

2.3.2 子传父

vue中子传父是使用自定义事件, 同样也是通过props进行传递

父组件:

changeCount(count) {this.setState({count: this.state.count + count})}render() {const { count } = this.statereturn (<div>Footer<h1>总计数:{count}</h1><AddCount changeCount={count => this.changeCount(count)} /></div>)}

子组件:

export class AddCount extends Component {render() {return (<div><button onClick={() => this.props.changeCount(1)}>+1</button><button onClick={() => this.props.changeCount(5)}>+5</button><button onClick={() => this.props.changeCount(10)}>+10</button></div>)}
}AddCount.propTypes = {changeCount: PropTypes.func.isRequired
}

2.4 组件插槽

React中没有对插槽的明确定义,但是可以通过下面两种方式实现:

  1. children
    React中创建虚拟DOM使用React.createElement([标签元素名称], {标签属性}, ...children),直接使用双标签书写组件,即可在双标签之间传入子组件。
    父组件:
<NavBar ><button>按钮</button><h2>标题</h2><p>...</p></NavBar>

子组件:

  render() {const { children } = this.propsreturn (<div className="nav-bar"><div className="left">{children[0]}</div><div className="center">{children[1]}</div><div className="right">{children[2]}</div></div>)}

但是这种情况有一个弊端:如果传入的children是一个数组,那么使用数组索引可以正常显示传入的传输;但若是传入单个元素,那么children将不会是数组的形式,那么使用数组索引进行展示将会报错;同样的,传入数组,使用单标签显示虽然代码不报错,但是不会出现想要的效果。

  1. props(推荐!!! )

父组件:

  const btn = <button>props按钮</button>const title = <h1>props标题</h1>const ellipse = <p>...</p>...<NavBar leftSlot={btn} centerSlot={title} rightSlot={ellipse} />...

子组件接收数据跟父传子一样,这里不再过多赘述。

作用域插槽的实现:
跟Vue类似,都是由子组件决定插槽传入的内容。这里不做赘述。

2.5 非父子通信

组件传参简写方式

const { info } = {name: 'cr',age: 30}
...
<ContextTest name={info.name} age={info.age} />
<ContextTest {...info} />
...

2.5.1 context

contextReact官方封装的全局传参的一种方式,类似与vue中的provideinject,使用较为麻烦,项目中不常用,更多的是使用Redux

  1. theme-context.js

在单独文件中创建theme-context,方便在多个文件中使用

import React from "react"// 这里传递的值是默认值,之后若使用到<ThemeContext.Provider value={{}}>进行传值,那么默认值将会被改变
const ThemeContext = React.createContext({'color': 'red','font-size': '20px'}
)export default ThemeContext
  1. 传递:
    使用<ThemeContext.Provider>进行传递,value是必填项
import ThemeContext from './Components/context/theme-context'
...
<ThemeContext.Provider value={{'color': 'red','font-size': '20px'}}><ContextTest></ContextTest></ThemeContext.Provider><Footer />
  1. 接收:
...class ... {render(){// 在render中就可以通过this.context使用传递过来的数据了return ()}
}
// 为当前组件绑定指定的context
ContextTest.contextType = ThemeContextexport default ContextTest

注:

在函数式组件中怎么使用context

在函数式组件中直接为当前组件指定context是不可以的,因为函数式组件没有实例,无法添加contextType,这时候可以通过<ThemeContext.Consumer>进行数据的接收。

import ThemeContext from "./context/theme-context";
// 函数式组件function AppFunc() {// 返回值与类组件返回值一致return (<div><ThemeContext.Consumer>{value => {console.log('value', value); return <div>{value.color}</div>}}</ThemeContext.Consumer></div>)
}export default AppFunc

当然了,<ThemeContext.Consumer>也不止这一种用法,类组件中contextType只能绑定一个context,当我们需要下一个组件中同时使用到多个context时就可以使用<ThemeContext.Consumer>

  • 一个Provider可以和多个消费组件有对应关系;
  • 多个Provider也可以嵌套使用,里层的会覆盖外层的数据;

2.5.2 事件总线

vue类似,不在过多赘述,知道在React中也可以使用(并且会使用)即可。

2.6 setState使用详解

2.6.1 为什么使用setState?

如果我们直接通过this.state.xxx="..."来修改state中的数据,那么页面数据不会更改,这是因为React不能感知到这种修改;

这种修改方式在vue中能够行得通是因为vue使用Object.defineProperty()或者Proxy做了数据劫持来监听数据变化;

React中我们必须通过setState来告知数据发生变化。

2.6.2 setState的详细使用

class App extends React.Component {constructor(props) {// 传入props  自动保存父组件传递的数据 super(props)this.state = {name: 'App',info: {name: 'cr',age: 30}}}this.setState({name: 'aa'})
}

这里的setState其实是又创建了一个新对象,之前的this.state是旧对象,数据的替换是通过Object.abssign(this.state, newState)将新旧数据进行了合并;

那也就是说我们在使用this.setState()时只要给这个函数传入一个对象即可,那我们可以直接传入对象,也可以传入一个返回对象的回调函数。

// 第一种
this.setState({name: 'aa'})// 第二种
this.setState(()=>{return {name: 'aa'}})

推荐第二种方式,

  1. 首先,我们可以在回调函数中对state数据做些处理;
  2. 其次,在这个回调函数中我们可以直接拿到之前的stateprops值,比第一种方式少了this的使用;
this.setState((state, props)=>{return {name: 'aa'}
})

setState()React的事件处理中是一个异步调用!

如果希望在数据更新之后,获取到对应的结果执行一些逻辑代码,那么就可以在setState()中传入第三个参数:callback

this.setState({name:'2222'}, ( )=>{console.log(this.state.name) // 2222
})
console.log(this.state.name) // aa

面试题:为什么setState设计为异步

  1. 显著提升性能
    如果同时有多个setState()产生,一次setState就执行一次render,界面多次渲染,这样会太消耗性能;
    所以React选择获取到多次更新,批量执行setState()更新

  2. 如果同步更新了state,但是还没有执行render,就会导致stateprops不同步

setState一定是异步更新吗?

不是的。

React18之前,把setState放在setTimeOut或者原生DOM中,那么它就是同步执行的,因为setState()被交给了浏览器,不再是React负责了;

而在React18之后,默认所有的操作都被放到了批处理中,都变成了异步操作;

http://www.dtcms.com/a/390014.html

相关文章:

  • C语言实现MATLAB中的Fir1带通滤波器
  • 微信小程序开发教程(十七)
  • 9月18日星期四今日早报简报微语报早读
  • SqlSugar 问题记录
  • 记一次宝塔+nginx+php8+thinkphp8多应用下某个应用报错404的问题 - nginx、php日志全无 - 无法追踪
  • Windows Server远程桌面(RDP)安全优化
  • 工具链过于分散会导致哪些问题
  • 【RAG】Youtu-GraphRAG
  • 惠普LaserJet Pro M203dn黑白激光打印机双面卡纸维修一例
  • 专题二 二叉树中的深度优先搜索
  • Git 多人协作(1)
  • 设计模式第三章(迭代器模式)
  • 网络原理(4):HTTP协议 -- HTTP请求 -- 首行(请求方法)
  • 密钥下发服务中心:双重验证 + 实时监控的轻量级密钥管理解决方案
  • 硬件 - RK3588部分(4) - 原理图 - RK806
  • Sass开发【三】
  • 百度之星2025(第二场)
  • Ovis-U1:阿里巴巴推出的统一的多模态理解与生成模型
  • 深入剖析C++智能指针:unique_ptr与shared_ptr的资源管理哲学
  • 创建索引失败,表一直查询不了
  • 知识分享:网线和DB9正确接线方法
  • 【算法笔记】前缀树
  • 让ai完成原神调酒 试做
  • 第十四届蓝桥杯青少组C++选拔赛[2022.11.27]第二部分编程题(2、拼写单词)
  • 私有化部署UE像素流后,通过实时云渲染平台配置网络端口,实现云推流内网及公网访问
  • Day 05 Geant4多线程 Multithreading --------以B1为例
  • 【word解析】从 Word 提取数学公式并渲染到 Web 页面的完整指南
  • FreeRTOS 队列机制详解:阻塞、唤醒与任务同步
  • Unity学习之UI优化总结
  • 基于微信小程序蓝牙信标 (Beacon)的室内导航实例