最近开始复习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 (
<Fragment>
<div>
<input type="text"/>
<button>提交</button>
</div>
<ul>
<li>学英语</li>
<li>学医学</li>
</ul>
</Fragment>
)
}
}
自定义组件必须以大写字母开头
// 注意引入的组件,要以大写开头
ReactDOM.render(<Todolist />, document.getElementById('root'));
// 不能写小写
// ReactDOM.render(<todolist />, 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 = "<mark>这是html内容</mark>"
const htmlObj = { __html: htmlContent }
// 返回一个标签时,则不用()包裹
return <p dangerouslySetInnerHTML={htmlObj}></p>
// return (
// // 多行内容return返回时用()包裹,且最外层只能有一个容器
// // Fragment相当于vue中的template作用
// <Fragment>
// <p>多行内容</p>
// <p dangerouslySetInnerHTML={htmlObj}></p>
// </Fragment>
// )
}
}
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`?
<input className="input" />
注释
{/* 使用bind改变this指向 */}
或
{
//使用bind改变this指向
}
使用不转义的html
使用dangerouslySetInnerHTML={{__html: xxx}}
,dangerouslySetInnerHTML返回是个包含html
内容的对象,注意__html
是规定的属性名
import React, { Component, Fragment } from 'react'
class HtmlCotent extends Component {
render() {
const htmlContent = '<mark>使用原生html</mark>'
const rawHtmlContent = { __html: htmlContent}
return (
<Fragment>
{/* html模板 */}
<p dangerouslySetInnerHTML={rawHtmlContent}></p>
</Fragment>
)
}
}
export default HtmlCotent;
使用htmlFor实现点击某处,聚焦在其它处
render() {
return (
<Fragment>
<div>
{/* 使用bind改变this指向 */}
{/* 想实现点击label时,聚焦在input框上,使用htmlFor实现 */}
<label htmlFor="inputContent">输入内容:</label>
<input type="text"
id="inputContent"
className="input"
value={this.state.inputValue}
onChange={ this.handleChange.bind(this) }
/>
<button onClick={this.handleClick.bind(this)}>提交</button>
</div>
</Fragment>
)
}
条件渲染
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 <p>hello</p>
}else {
return <p>byebye</p>
}
// 三元表达式
// return (this.state.show ? <p>hello</p> : <p>byebye</p>)
// 与运算&&,或运算||
// return this.state.show && <p>hello</p>
// return this.state.show || <p>byebye</p>
}
}
export default Condition
suspense
suspense
通常和异步组件配合,用于在异步组件未加载时,添加等待动画或其它操作等
import React,{ Component, Suspense } from 'react';
const AsyncComp = React.lazy(() => import('./FormInput'))
class SuspenseDemo extends Component {
render() {
// fallback代表异步操作之前的展示效果
return <Suspense fallback={<div>Loading...</div>}>
{/* 这里是异步引入的组件 */}
<AsyncComp/>
</Suspense>
}
}
export default SuspenseDemo;
循环渲染
使用map
渲染,key的使用同vue
,一般都不要设为index
或random
// 循环渲染
return (
<ul>
{this.state.list.map(item =>
{
return <li key={item.id}>{item.age}</li>
}
)}
</ul>
)
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 (
<div>
<p>{this.state.count}</p>
<button onClick={this.addCount}>累加</button>
</div>
)
}
}
export default SetStateDemo;
事件
事件要使用
on
+大写驼峰
的写法,比如onClink={this.xxx}
,等同于vue中的@click="xxx"
-
关于事件的
this
指向:- 第一种写法:
- 使用
ES5
方式定义事件,this
会默认为undefined
,要使用bind
来绑定this
指向,否则会报错Cannot read property 'setState' of undefined
- 事件的
bind(this)
最好写在constructor
中,这样只会执行一次bind
并缓存结果,如果写在标签上,则点击一次就要执行一次bind
,且多处使用时,多处也要bind
,所以写在constructor
中是一种性能优化
- 使用
- 第二种写法,使用
ES6
箭头函数,this
会默认指向当前实例,不需要考虑this
指向,但可能需要babel
来转译
- 第一种写法:
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 <p onClick={this.handleClick.bind(this)}>{this.state.name}</p>
// 要使用onXxxx的写法绑定事件
return (
<div>
{/* 第一种写法 */}
<p onClick={this.handleClick}>{this.state.name}</p>
{/* 第二种写法 */}
<p onClick={this.handleClick2}>{this.state.name}</p>
</div>
)
}
// 第一种定义方法
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 (
<div>
{/* react事件性质 */}
<a href="http://www.baidu111.com" onClick={this.handleClick3}>跳转链接</a>
</div>
)
}
// 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);
// 原生事件的对象,<a href="http://www.baidu111.com">跳转链接</a>
console.log('nativeEvent target:', event.nativeEvent.target)
// 事件挂载的对象,react16前是挂载到document上的,react17后是挂载到root元素
console.log('nativeEvent currentTarget:', event.nativeEvent.currentTarget )
}
- 事件的传参
- 使用
bind
传参,自定义参数从第二个开始传
<p onClick={this.handleClick4.bind(this, 'aa', 'bb')}>事件传参一</p>
// event是默认添加到最后一个形参中的
handleClick4(a, b, event) {
console.log('参数:', a, b, event); // 参数: aa bb SyntheticBaseEvent
}
- 使用箭头函数,
event
需要定义在返回的函数中
<p onClick={this.handleClick5('aa')}>事件传参2</p>
// 需要返回一个函数,event作为返回函数的参数
handleClick5 = (a) => {
return (event) => {
console.log('a', a); // aa
console.log('e', event); // SyntheticBaseEvent
}
}
- 在绑定时,使用箭头函数,将
event
参数传入,推荐使用这种,写法方便简洁一些
<p onClick={(e) => this.handleClick6('aa', e)}>事件传参3</p>
// 此时,就不需要返回一个函数了
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 <div>
<p>{name}</p>
<label htmlFor="inputName">姓名:</label>
<input type="text" id="inputName" value={name} onChange={this.inputChange}/>
<hr/>
<p>个人信息:<span>{info}</span></p>
<textarea value={info} onChange={this.textareaChange}></textarea>
<hr/>
<p>城市:<span>{city}</span></p>
<select value={city} onChange={this.selectChange}>
<option value="beijing">北京</option>
<option value="shenzhen">深圳</option>
<option value="shangehai">上海</option>
</select>
<hr/>
<p>性别:<span>{gender}</span></p>
男 <input type="radio" name="gender" value="male" checked={gender === 'male'} onChange={this.radioChange}/>
女 <input type="radio" name="gender" value="female" checked={gender === 'female'} onChange={this.radioChange}/>
<hr/>
<p>喜好:{like.map(item => {
return <span>{item}</span>
})}</p>
<input type="checkbox" value="basketball" checked={like.includes('basketball')} onChange={this.checkboxChange}/>篮球
<input type="checkbox" value="football" checked={like.includes('football')} onChange={this.checkboxChange}/>足球
</div>
}
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 (
<TodoItem
content={item}
index={index}
deleteItem={this.handleDelete.bind(this)}
/>
)
}
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 (
// 不优化之前的写法是:
//<div onClick={this.handleClick.bind(this)}></div>
<div onClick={this.handleClick}></div>
)
}
handleClick() {
// 触发父组件方法
this.props.deleteItem(this.props.index)
}
}