create-react-app
创建 react
项目的脚手架。
React 基本用法
jsx 语法
变量、表达式
javascript
import React from 'react';
class JSXBaseDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '章三'
};
}
render() {
// 获取变量 插值
const pElem = <p>{this.state.name}</p>;
return pElem;
// // 表达式
// const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>;
// return exprElem;
}
}
export default JSXBaseDemo;
class、style
css
/* style.css */
.title {
font-size: 30px;
color: red;
}
javascript
import React from 'react';
import './style.css';
class JSXBaseDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
// class
const classElem = <p className="title">设置 css class</p>;
return classElem;
// // style
// const styleData = { fontSize: '30px', color: 'blue' };
// const styleElem = <p style={styleData}>设置 style</p>;
// // 内联写法,注意 {{ 和 }}
// // const styleElem = <p style={{ fontSize: '30px', color: 'blue' }}>设置 style</p>;
// return styleElem;
}
}
export default JSXBaseDemo;
子元素和组件
javascript
import React from 'react';
import List from '../List';
class JSXBaseDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
imgUrl: 'https://img1.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
};
}
render() {
// 子元素
const imgElem = <div>
<p>我的头像</p>
<img src="xxxx.png"/>
<img src={this.state.imgUrl}/>
</div>;
return imgElem';
// // 加载组件
// const componentElem = <div>
// <p>JSX 中加载一个组件</p>
// <hr />
// <List />
// </div>;
// return componentElem;
}
}
export default JSXBaseDemo;
原生 html
javascript
import React from 'react';
class JSXBaseDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
// 原生 html
const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>';
const rawHtmlData = {
__html: rawHtml // 注意,必须是这种格式
};
const rawHtmlElem = <div>
<p dangerouslySetInnerHTML={rawHtmlData}></p>
<p>{rawHtml}</p>
</div>;
return rawHtmlElem;
}
}
export default JSXBaseDemo;
条件
if else
- 三元表达式
- 逻辑运算符:
&&
、||
css
.btn-white {
color: #333;
}
.btn-black {
background-color: #666;
color: #fff;;
}
javascript
import React from 'react';
import './style.css';
class ConditionDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: 'black'
};
}
render() {
const blackBtn = <button className="btn-black">black btn</button>;
const whiteBtn = <button className="btn-white">white btn</button>;
// // if else
// if (this.state.theme === 'black') {
// return blackBtn;
// } else {
// return whiteBtn;
// }
// // 三元运算符
// return <div>
// { this.state.theme === 'black' ? blackBtn : whiteBtn }
// </div>;
// &&
return <div>
{ this.state.theme === 'black' && blackBtn }
</div>;
}
}
export default ConditionDemo;
列表渲染
map
key
javascript
import React from 'react';
class ListDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
};
}
render() {
return <ul>
{
this.state.list.map(
(item, index) => {
// 这里的 key 和 Vue 的 key 类似,必填,不能是 index 或 random
return <li key={item.id}>
index {index}; id {item.id}; title {item.title}
</li>;
}
)
}
</ul>;
}
}
export default ListDemo;
事件
bind this
- 非严格模式下,
dom
事件中的this
都是运行时的,就是浏览器执行的,指向的是window
- 严格模式下,这里的
this
指向undefined
箭头函数为什么能解决 bind this
?
- 箭头函数的
this
永远指向上级作用域。 - 箭头函数的
this
是在定义函数时绑定的,不是在执行过程中绑定的。 - 也就是说,函数在定义时,
this
就继承了定义函数的对象。
javascript
import React from 'react';
class EventDemo extends React.Component {
constructor(props) {
super(props);;
this.state = {
name: 'zhangsan'
};
// 修改方法的 this 指向
this.clickHandler1 = this.clickHandler1.bind(this);
}
render() {
// this - 使用 bind
return <p onClick={this.clickHandler1}>
{this.state.name}
</p>;
// // this - 使用静态方法
// return <p onClick={this.clickHandler2}>
// clickHandler2 {this.state.name}
// </p>;
}
clickHandler1() {
// console.log('this....', this)
// this 默认是 undefined
this.setState({
name: 'lisi'
});
}
// 静态方法,this 指向当前实例
clickHandler2 = () => {
this.setState({
name: 'lisi'
});
}
}
export default EventDemo;
关于 event 参数
javascript
import React from 'react';
class EventDemo extends React.Component {
constructor(props) {
super(props);;
this.state = {
};
}
render() {
// event
return <a href="https://imooc.com/" onClick={this.clickHandler3}>
click me
</a>;
}
// 获取 event
clickHandler3 = (event) => {
// 阻止默认行为
event.preventDefault();
// 阻止冒泡
event.stopPropagation();
// 指向当前元素,即当前元素触发
console.log('target', event.target);
// 指向当前元素,假象!!!
console.log('current target', event.currentTarget);
// 注意,event 其实是 React 封装的。
// 可以看 __proto__.constructor 是 SyntheticEvent 组合事件
// 不是原生的 Event ,原生的 MouseEvent
console.log('event', event);
console.log('event.__proto__.constructor', event.__proto__.constructor);
// 原生 event 如下。其 __proto__.constructor 是 MouseEvent
console.log('nativeEvent', event.nativeEvent);
// 指向当前元素,即当前元素触发
console.log('nativeEvent target', event.nativeEvent.target);
// 指向 document !!!
console.log('nativeEvent current target', event.nativeEvent.currentTarget;);
// 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
// 2. event.nativeEvent 是原生事件对象
// 3. 所有的事件,都被挂载到 document 上 (React17 事件绑定到 root 上)
// 4. 和 DOM 事件不一样,和 Vue 事件也不一样
}
}
export default EventDemo;
传递自定义参数
javascript
import React from 'react';
class EventDemo extends React.Component {
constructor(props) {
super(props);;
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
};
}
render() {
// 传递参数 - 用 bind(this, a, b)
return <ul>
{
this.state.list.map((item, index) => {
return <li key={item.id} onClick={this.clickHandler4.bind(this, item.id, item.title)}>
index {index}; title {item.title}
</li>;
})
}
</ul>;
}
// 传递参数
// 最后追加一个参数,即可接收 event
clickHandler4(id, title, event) {
console.log(id, title);
console.log('event', event);
}
}
export default EventDemo;
组件和 props (类型检查)
受控组件
- 表单的值受
state
控制。 value
指向state
,onChange
事件监听,使用setState
修改值。
javascript
import React from 'react';
class FormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'zhangsan'
};
}
render() {
// 受控组件
return <div>
<p>{this.state.name}</p>
<label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */}
<input id="inputName" value={this.state.name} onChange={this.onInputChange} />
</div>;
}
onInputChange = (e) => {
this.setState({
name: e.target.value
});
}
}
export default FormDemo;
表单的使用
javascript
import React from 'react';
class FormDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'zhangsan',
info: '个人信息',
city: 'beijing',
flag: true,
gender: 'male'
};
}
render() {
// // 受控组件
// return <div>
// <p>{this.state.name}</p>
// <label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */}
// <input id="inputName" value={this.state.name} onChange={this.onInputChange}/>
// </div>;
// textarea - 使用 value
return <div>
<textarea value={this.state.info} onChange={this.onTextareaChange}/>
<p>{this.state.info}</p>
</div>;
// // select - 使用 value
// return <div>
// <select value={this.state.city} onChange={this.onSelectChange}>
// <option value="beijing">北京</option>
// <option value="shanghai">上海</option>
// <option value="shenzhen">深圳</option>
// </select>
// <p>{this.state.city}</p>
// </div>;
// // checkbox
// return <div>
// <input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/>
// <p>{this.state.flag.toString()}</p>
// </div>;
// // radio
// return <div>
// male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>
// female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/>
// <p>{this.state.gender}</p>
// </div>;
}
onInputChange = (e) => {
this.setState({
name: e.target.value
});
}
onTextareaChange = (e) => {
this.setState({
info: e.target.value
});
}
onSelectChange = (e) => {
this.setState({
city: e.target.value
});
}
onCheckboxChange = () => {
this.setState({
flag: !this.state.flag
});
}
onRadioChange = (e) => {
this.setState({
gender: e.target.value
});
}
}
export default FormDemo;
组件使用
props
传递数据props
传递函数props
类型检查
javascript
import React from 'react';
import PropTypes from 'prop-types';
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {
title: ''
};
}
render() {
return <div>
<input value={this.state.title} onChange={this.onTitleChange}/>
<button onClick={this.onSubmit}>提交</button>
</div>;
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
});
}
onSubmit = () => {
const { submitTitle } = this.props;
submitTitle(this.state.title); // 'abc'
this.setState({
title: ''
});
}
}
// props 类型检查
Input.propTypes = {
submitTitle: PropTypes.func.isRequired
};
class List extends React.Component {
constructor(props) {
super(props);
}
render() {
const { list } = this.props;
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>;
})}</ul>;
}
}
// props 类型检查
List.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired
};
class Footer extends React.Component {
constructor(props) {
super(props);
}
render() {
return <p>
{this.props.text}
{this.props.length}
</p>;
}
}
class TodoListDemo extends React.Component {
constructor(props) {
super(props);
// 状态(数据)提升
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
],
footerInfo: '底部文字'
};
}
render() {
return <div>
<Input submitTitle={this.onSubmitTitle}/>
<List list={this.state.list}/>
<Footer text={this.state.footerInfo} length={this.state.list.length}/>
</div>;
}
onSubmitTitle = (title) => {
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title
})
});
}
}
export default TodoListDemo;
state 和 setState
setState
- 不可变值
- 可能是异步更新 (针对
React <= 17
)- 直接使用是异步更新,在第二个参数的回调函数中可以拿到最新的
state
- 在
setTimeout
中使用是同步 - 自定义的
dom
事件,是同步的
- 直接使用是异步更新,在第二个参数的回调函数中可以拿到最新的
- 可能会被合并 (针对
React <= 17
)- 异步更新,
setState
传入的参数是对象,那么在更新前会被合并 - 异步更新,
setState
传入的参数是函数,不会被合并
- 异步更新,
javascript
import React from 'react';
class StateDemo extends React.Component {
constructor(props) {
super(props);
// 第一,state 要在构造函数中定义
this.state = {
count: 0
};
}
render() {
return <div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>;
}
increase = () => {
// // 第二,不要直接修改 state ,要使用不可变值 -----------------------
// // this.state.count++; // 错误
// this.setState({
// count: this.state.count + 1 // SCU
// });
// 操作数组、对象的的常用形式
// 第三,setState 可能是异步更新(有可能是同步更新) ------------------
// this.setState({
// count: this.state.count + 1
// }, () => {
// // 联想 Vue $nextTick - DOM
// // 回调函数中可以拿到最新的 state
// console.log('count by callback', this.state.count);
// });
// console.log('count', this.state.count); // 异步的,拿不到最新值
// // setTimeout 中 setState 是同步的
// setTimeout(() => {
// this.setState({
// count: this.state.count + 1
// });
// console.log('count in setTimeout', this.state.count);
// }, 0);
// 自己定义的 DOM 事件,setState 是同步的。
// 第四,state 异步更新的话,更新前会被合并 --------------------------
// // 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
// this.setState({
// count: this.state.count + 1
// });
// this.setState({
// count: this.state.count + 1
// });
// this.setState({
// count: this.state.count + 1
// });
// 传入函数,不会被合并。执行结果是 +3
this.setState((prevState, props) => {
return {
count: prevState.count + 1
};
});
this.setState((prevState, props) => {
return {
count: prevState.count + 1
};
});
this.setState((prevState, props) => {
return {
count: prevState.count + 1
};
});
}
// bodyClickHandler = () => {
// this.setState({
// count: this.state.count + 1
// });
// console.log('count in body event', this.state.count);
// }
// componentDidMount() {
// // 自己定义的 DOM 事件,setState 是同步的
// document.body.addEventListener('click', this.bodyClickHandler);
// }
// componentWillUnmount() {
// // 及时销毁自定义 DOM 事件
// document.body.removeEventListener('click', this.bodyClickHandler);
// // clearTimeout
// }
}
export default StateDemo;
// -------------------------- 我是分割线 -----------------------------
// // 不可变值(函数式编程,纯函数) - 数组
// const list5Copy = this.state.list5.slice();
// list5Copy.splice(2, 0, 'a'); // 中间插入/删除
// this.setState({
// list1: this.state.list1.concat(100), // 追加
// list2: [...this.state.list2, 100], // 追加
// list3: this.state.list3.slice(0, 3), // 截取
// list4: this.state.list4.filter(item => item > 100), // 筛选
// list5: list5Copy // 其他操作
// });
// // 注意,不能直接对 this.state.list 进行 push pop shift unshift splice 等,这样违反不可变值
// // 不可变值 - 对象
// this.setState({
// obj1: Object.assign({}, this.state.obj1, {a: 100}),
// obj2: {...this.state.obj2, a: 100}
// })
// // 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值
React18 中的 setState
React
组件事件:异步更新 + 合并state
DOM
事件、setTimeout
:异步更新 + 合并state
Automatic Batching
自动批处理
javascript
import { useState, useEffect } from 'react';
function useStateDemo() {
const [value, setValue]= useState(100);
function clickHandler() {
// // 合并后 +1
// setValue(value + 1);
// setValue(value + 1);
// setValue(value + 1);
// console.log(value); // 100 异步更新
setTimeout(() => {
// 合并后 +1
setValue(value + 1);
setValue(value + 1);
setValue(value + 1);
console.log(value); // 100 异步更新
});
}
useEffect(() =>{
// 自绑定 DOM 事件
document.getElementById('btn2').addEventListener('click', () => {
// 合并后 +1
setValue(value + 1);
setValue(value + 1);
setValue(value + 1);
console.log(value); // 100 异步更新
});
});
return <div>
<span>{value}</span>
<button onClick={clickHandler}>increase1</button>
<button id="btn2">increase2</button>
</div>;
}
React <= 17
:只有React
组件事件才批处理(合并 + 异步)React18
:所有事件都自动批处理Automatic Batching
组件生命周期
react 生命周期图示
- 初始化阶段:
constructor
- 挂载阶段:
componentDidMount
- 更新阶段:
componentDidUpdate
- 卸载阶段:
componentWillUnmount
展示不常用的生命周期图示
shouldComponentUpdate
:可以控制是否更新
父子组件生命周期
- 父组件先
constructor
,然后子组件才constructor
- 子组件先
componentDidMount
,然后父组件componentDidMount
- 子组件先
componentDidUpdate
,然后父组件componentDidUpdate
- 父组件先触发
componentWillUnmout
,然后子组件触发componentWillUnmount
FQA
React
事件为何bind this
?- 非严格模式下,
dom
事件中的this
都是运行时的,就是浏览器执行的,指向的是window
- 严格模式下,这里的
this
指向undefined
- 非严格模式下,
- 箭头函数为什么能解决
bind this
?- 箭头函数的
this
永远指向上级作用域:箭头函数的this
是在定义函数时绑定的,不是在执行过程中绑定的。也就是说,函数在定义时,this
就继承了定义函数的对象。
- 箭头函数的
react
的事件和vue
的区别。vue
的事件是原生event
(MouseEvent
),事件是绑定在当前元素上的react
的事件是SyntheticEvent
,React16
事件是绑定在document
上的,React17
后是绑定在root
上的;react
获取原生事件是通过event.nativeEvent
获取。
react
为什么对事件对象进行二次封装?- 兼容各个平台,不仅仅是
DOM
,如react native
。
- 兼容各个平台,不仅仅是
React17
为什么将事件绑定到root
上?- 有利于多个
React
版本并存(document
只有一个,而root
可以有多个),例如微前端。
- 有利于多个
- 描述
react
组件的生命周期。
- 初始化阶段:
constructor
- 挂载阶段:
componentDidMount
- 更阶段:
componentDidUpdate
- 卸载阶段:
componentWillUnmount
- 在更新阶段
componentDidUpdate
之前还有一个shouldComponentUpdate
:可以控制是否更新
react
组件如何通讯?- 父子组件通过
props
传递数据 - 可通过
context
从顶层组件向子孙组件进行下发数据 - 使用
redux
可以全局组件共享状态
- 父子组件通过