最近开始复习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)
}
}