最近开始复习react
特性了,这篇是用来记录复盘的
创建项目
npm i -g create-react-app
npx create-react-app react-demo
cd react-demo
npm start
jsx配置
要在jsx
中自动补齐标签,需要在settings.json
文件中添加以下配置
{
"emmet.includeLanguages": {
"javascript": "javascriptreact"
}
}
基础语法
Fragment
同vue一样,只允许·html
中最外层只能有一个容器,可以写个div
包含,如果不想让其在html
中显示,可以使用Fregment
替代(类似vue
中的template
标签),注意要使用大写开头
// 注意就算是里层的,只要是return返回的,最外层都只能有一个容器
import React, { Component, Fragment } from 'react'
class Todolist extends Component {
render() {
return (
提交
)
}
}
自定义组件必须以大写字母开头
// 注意引入的组件,要以大写开头
ReactDOM.render( , document.getElementById('root'));
// 不能写小写
// ReactDOM.render( , document.getElementById('root'));
类组件的使用
在constructor
构造函数中接收父组件属性props
,使用super
继承属性,使用this.state
设置自身的状态
如果只返回一个标签内容,直接return
如果有多个标签内容,则需要加括号包裹返回内容:return (...)
,且返回的内容最外层只能有一个容器
返回的最外层容器,如果不想显示,则可以使用Fragment
标签包裹,它的作用同vue中的template
标签,只是作容器,但不会渲染为dom
import React,{ Component, Fragment } from 'react';
class HtmlContent extends Component{
constructor(props) {
super(props)
this.state = {
name: 'xxx'
}
}
render() {
const htmlContent = "这是html内容 "
const htmlObj = { __html: htmlContent }
// 返回一个标签时,则不用()包裹
return
// return (
// // 多行内容return返回时用()包裹,且最外层只能有一个容器
// // Fragment相当于vue中的template作用
//
// 多行内容
//
//
// )
}
}
export default HtmlContent
要在react中想使用js语法,需要使用{}
react
中使用{}
作为插值的标识符,等同于vue
中的{{}}
,在其中可以任何js
的表达式
css类名: className
css
的类名使用className
, 而不是class
,因为react
在jsx
中认为class
与类名会有二义,所以使用className
,否则会警告: Warning: Invalid DOM property `class`. Did you mean `className`?
注释
{/* 使用bind改变this指向 */}
或
{
//使用bind改变this指向
}
使用不转义的html
使用dangerouslySetInnerHTML={{__html: xxx}}
,dangerouslySetInnerHTML返回是个包含html
内容的对象,注意__html
是规定的属性名
import React, { Component, Fragment } from 'react'
class HtmlCotent extends Component {
render() {
const htmlContent = '使用原生html '
const rawHtmlContent = { __html: htmlContent}
return (
{/* html模板 */}
)
}
}
export default HtmlCotent;
使用htmlFor实现点击某处,聚焦在其它处
render() {
return (
{/* 使用bind改变this指向 */}
{/* 想实现点击label时,聚焦在input框上,使用htmlFor实现 */}
输入内容:
提交
)
}
条件渲染
import React, {Component} from 'react'
class Condition extends Component {
constructor(props) {
super(props)
this.state = {
show: false,
list: [
{id: 1, age: 11},
{id: 2, age: 22},
{id: 3, age: 33},
]
}
}
render() {
// if else渲染
if(this.state.show) {
return hello
}else {
return byebye
}
// 三元表达式
// return (this.state.show ? hello
: byebye
)
// 与运算&&,或运算||
// return this.state.show && hello
// return this.state.show || byebye
}
}
export default Condition
suspense
suspense
通常和异步组件配合,用于在异步组件未加载时,添加等待动画或其它操作等
import React,{ Component, Suspense } from 'react';
const AsyncComp = React.lazy(() => import('./FormInput'))
class SuspenseDemo extends Component {
render() {
// fallback代表异步操作之前的展示效果
return Loading...
}>
{/* 这里是异步引入的组件 */}
}
}
export default SuspenseDemo;
循环渲染
使用map
渲染,key的使用同vue
,一般都不要设为index
或random
// 循环渲染
return (
{this.state.list.map(item =>
{
return {item.age}
}
)}
)
state状态
使用this.state
定义状态,要使用this.setState({xxx: xxx})
方式修改状态
state
中数据不可直接使用this.state.xxx = xxx
形式来改变状态,这是因为react
的immutable
概念决定的,它规定了state
中的数据不能被直接改变,必须使用setState
方式修改,必须将state
中数据拷贝一份来修改
import React, { Component } from 'react'
class SetStateDemo extends Component{
constructor(props) {
super(props)
this.state = {
count: 0,
list: [
{id: 1, age: 1},
{id: 2, age: 2}
]
}
}
addCount = () => {
// 建议使用这种,对原数据进行拷贝,在拷贝上修改
const newList = this.state.list.slice()
newList.push(3)
this.setState({
// 使用this.state.count++,会报警告,因为这句直接修改了原count的值:
// Do not mutate state directly. Use setState()
// count: this.state.count++
count: this.state.count + 1,
list: newList
})
console.log(this.state.count) // 这里是异步的
}
render() {
return (
)
}
}
export default SetStateDemo;
事件
import React, { Component } from 'react'
class Event extends Component {
constructor(props) {
super(props)
this.state = {
name: '小花',
}
// 第一种写法,绑定事件中的this
this.handleClick = this.handleClick.bind(this)
}
render() {
// 如果使用这句的话,当点击多次,就要触发多次bind
// 在constructor中只bind一次,保存缓存结果,算是一种性能优化
// return {this.state.name}
// 要使用onXxxx的写法绑定事件
return (
{/* 第一种写法 */}
{this.state.name}
{/* 第二种写法 */}
{this.state.name}
)
}
// 第一种定义方法
handleClick() {
// 通过setState修改数据
this.setState({
name: '小小'
})
}
// 第二种定义方法写法,使用箭头函数,使this指向当前实例
handleClick2 = (event) => {
console.log(event);
// 通过setState修改数据
this.setState({
name: '小小'
})
}
}
export default Event
事件的性质
在react
中,使用event
获取当前事件,但看到打印出的是SyntheticBaseEvent
,这是react
合成的事件,并不是原生的Mouse Event
,它模拟出了DOM事件的所有能力,包括冒泡、阻止事件等
可以通过event.nativeEvent
获取原生事件
所有的事件,在react16
以前,都被挂载到document
上;在react17
后,则会被挂载到root
根元素上,这样有利于多个React
版本并存,例如微前端;可以通过event.navtiveEvent.currentTarget
来获取挂载元素
这和dom
事件不一样,和vue
事件也不太一样,vue
的事件是原生的Mouse Event
,并会被挂载到当前元素
render() {
return (
)
}
// react的事件
handleClick3 = (event) => {
console.log('handleClick3');
// SyntheticBaseEvent,是react自身处理的合成事件,不是原生的Mouse Event
// 这是跟vue不同的,vue的event是原生的,且被挂载到当前元素,通过$event获取
console.log(event)
event.preventDefault() // 阻止原生操作,比如a标签的跳转,右链菜单等
event.stopPropagation() // 阻止冒泡
// 获取原生事件 MouseEvent
console.log('event nativeEvent:', event.nativeEvent);
// 原生事件的对象,跳转链接
console.log('nativeEvent target:', event.nativeEvent.target)
// 事件挂载的对象,react16前是挂载到document上的,react17后是挂载到root元素
console.log('nativeEvent currentTarget:', event.nativeEvent.currentTarget )
}
使用bind
传参,自定义参数从第二个开始传
事件传参一
// event是默认添加到最后一个形参中的
handleClick4(a, b, event) {
console.log('参数:', a, b, event); // 参数: aa bb SyntheticBaseEvent
}
使用箭头函数,event
需要定义在返回的函数中
事件传参2
// 需要返回一个函数,event作为返回函数的参数
handleClick5 = (a) => {
return (event) => {
console.log('a', a); // aa
console.log('e', event); // SyntheticBaseEvent
}
}
在绑定时,使用箭头函数,将event
参数传入,推荐使用这种,写法方便简洁一些
this.handleClick6('aa', e)}>事件传参3
// 此时,就不需要返回一个函数了
handleClick6 = (a, event) => {
console.log(a);
console.log(event)
}
受控组件
受控组件,如其名,意思是组件的状态受react
控制。在vue
中,表单实现双向绑定时,是使用v-model
来实现的,但在react
中,并没有提供类似的api,所以需要自己实现:
对于input
、textarea
、select
等都是通过控制value
值来控制表单内容,通过onChange
来监听表单输入
对于radio
、checkbox
等是通过控制checked
值来控制表单内容,通过onChange
来监听表单输入
import React, { Component } from 'react';
class FormInput extends Component {
constructor(props) {
super(props)
this.state = {
name: '小花',
info: 'xxxx',
city: 'shenzhen',
flag: true,
gender: 'female',
like: ['basketball']
}
}
render() {
let { name, info, city, gender, like } = this.state
// 表单受控组件
return
}
inputChange = (e) => {
this.setState({
name: e.target.value
})
}
textareaChange = (e) => {
this.setState({
info: e.target.value
})
}
selectChange = (e) => {
this.setState({
city: e.target.value
})
}
radioChange = (e) => {
this.setState({
gender: e.target.value
})
}
checkboxChange = (e) => {
const val = e.target.value
let newLike = this.state.like.slice();
if(this.state.like.indexOf(val) !== -1) {
let index = this.state.like.indexOf(val)
newLike.splice(index, 1)
}else {
newLike.push(val)
}
this.setState({
like: newLike
})
}
}
export default FormInput
父子组件间通信
和vue
类似,但父组件方法和属性都是通过属性方式传递(不同于vue
的事件是通过v-on/@
传递),子组件不是通过emit
方式触发父组件方法,而是通过this.props.parentMethod
方式来触发
// 父组件
render() {
return (
)
}
handleDelete=(index) =>{
// immutable概念,不允许在state上直接修改数据,否则后面性能优化可能存在问题
let list = [...this.state.list] // 拷贝一个副本,在此上面作修改
list.splice(index,1)
this.setState({
list: list
})
}
// 子组件
import React, { Component } from 'react'
class TodoItem extends Component {
constructor(props) {
super(props);
// 将this指向当前react组件,在构造函数时就指定this指向,有利于性能优化
this.handleClick = this.handleClick.bind(this)
}
render() {
return (
// 不优化之前的写法是:
//
)
}
handleClick() {
// 触发父组件方法
this.props.deleteItem(this.props.index)
}
}