1.新建react项目
npx:
该方式不用全局下载脚手架,直接运行脚手架的命令
原理:先下载脚手架生成项目,完了再删掉脚手架
npx create-react-app my-app
npm start
全局安装脚手架来创建项目:
//全局安装脚手架
npm i -g create-react-app
//新建react项目
create-react-app my-app
npm start
2.js文件被看做是一个组件
当js文件中引入React时,本js文件会被看作是一个组件;若没被引用,则是普通js文件
import React from 'react;
function App(){
return (
<div className="App">
根节点
</div>
);
}
export default App;
3.组件
1.函数组件
函数组件性能最好,没有生命周期
函数组件没有组件内部的数据
function App(){
return (
<div className="App">
根节点
</div>
);
}
//另一种写法
function App(){
//return这一行必须有代码在,否则会报错
return <div className="App">根节点</div>
}
2.箭头函数组件
const App = () =>{
return <div>123</div>
}
//再精简
const App = () => <div>123</div>
3.类组件
性能没有函数组件好,有生命周期
有组件内部的数据
import React from 'react';
//声明一个类class
//必须要继承一个父类,表明当前class是一个react组件
//类组件必须提供一个render方法,方法里面必须返回标签
class App extends React.Component{
render(){
return <div>
123
<Bpp />//类组件嵌套使用
</div>
}
}
class Bpp extends React.Component{
render(){
return <div>345</div>
}
}
export default App;
tips:什么叫做组件内部的数据?
3.组件命名规范
函数组件命名:首字母大写
标签里面没有东西的话,可以改为单标签即<Home></Home>改为<Home />
4.模板规范
let num = 100;
let str = '哈哈';
let bo = true;//react不能直接渲染boolean类型
let obj = { //不能直接渲染复杂类型
name:'嘿嘿'
};
function App(){
return <div>
{num}
{str}
{'' + bo}
{obj}
{JSON.stringify(obj)}
</div>
}
5.JSX的概念
JSX :javascript xml
react中使用JSX语法,可以直接在js代码中写标签,在标签中写js代码,非常灵活,但可读性因水平而异。
6.简单使用JSX
js与html标签相互嵌套使用
let num = 123;
function App(){
return <div> { <div> 哈{num}哈 </div> } </div>
}
7.将函数组件看成一个普通函数来调用
function aaa(){
return <div>AAA</div>
}
//这时候aaa函数会作为一个普通函数来执行,并不会发生异常
//问题:不是首字母应该大写吗?
//当函数作为一个组件来执行时,需要首字母大写:<Aaa></Aaa>
//当函数作为一个普通函数执行时,则不受约束
function App(){
return <div> { aaa() } </div>
}
//aaa函数作为组件执行时
//此时首字母非大写,会抛异常
function App(){
return <div><aaa /></div>
}
8.循环语法
function App(){
return <div>
<p>哈哈1<p>
<p>哈哈2<p>
<p>哈哈3<p>
</div>
}
//在jsx中使用map做映射并把数据返回
//不加key会抛异常,但并不影响运行
let list = ['哈哈1','哈哈2','哈哈3']
function App(){
return <div>
{list.map(function(item){
return <p key={item}>{item}</p>
})}
</div>
}
//再精简
function App(){
return <div>{list.map(item => <p key={item}>{item}</p>)}</div>
}
9.组件内部的数据
state允许组件自己修改数据
1.函数组件没有自己内部的数据
函数组件数据无法响应式
let num = 0;
function addNum(){
num++;
console.log(num);
}
function App(){
return (
<div className='App'>
<button onClick={addNum}>点击增加 num值</button>
函数组件 { num }
</div>
);
}
//当点击按钮时,会打印+1后的num值,但函数组件中的num值并不会被更改
//但num改变时,并没有什么东西会通知函数组件进行改变,即这中间没有数据劫持,订阅通知等等操作。所以函数组件性能最好
2.类组件的内部数据
点击按钮时,num自增并渲染到页面上
//state 类似于vue中的data
class App extends React.Component{
state = {
num: 100
}
addNum = () => {
this.setState({
num: ++this.state.num
});
}
render(){
return <div>
<button onClick={this.addNum}>改变num</button>
数据 {this.state.num}
</div>
}
}
tips:
this.setState({},() => {})是异步修改state中的值的,所以如需在修改之后立即获取修改后的值,则在它的回调函数中去获取。
10.状态state定义的两种方式
1.在类的属性上定义
class App extends React.Component{
state = {
num:11
}
render(){
return <div>{this.state.num}</div>
}
}
2. 直接在类的构造函数中定义
class App extends Component{
constructor(){
super();
this.state = {
num: 10
};
}
render(){
return <div> {this.state.num} </div>
}
}
11.错误的修改state
this.setState({})方法修改state会触发render方法更新视图,render方法一执行又会去修改state,所以会抛出递归超出最大层级的异常
所以禁止在render中直接修改数据
class App{
state = {
num:10
}
render(){
this.setState({
num: 200
});
return <div> {this.state.num} </div>
}
}
12.属性
react中父组件给子组件传递的数据,就叫属性。子组件中不能直接修改props的数据
//数据写死
class App extends React.Component{
render(){
return <div>
<Btn aaa="按钮" bbb="哈哈"/>
</div>
}
}
//数据放到state中
class App extends React.Component{
state = {
aaa:'按钮',
bbb:'哈哈'
}
render(){
return <div>
<Btn aaa={aaa} bbb={bbb}/>
//多个值的时候可以使用展开运算符
//vue3同样支持
<Btn {...this.state} />
</div>
}
}
class Btn extends React.Component{
render(){
return <div>
<button>{this.props.aaa} - {this.props.bbb}</button>
</div>
}
}
13.属性的默认值
子组件中定义static defaultProps = {} 来说明属性的默认值
class App extends React.Component{
render(){
return <div>
<Btn btnname='按钮的新值'/>
</div>
}
}
class Btn extends React.Component{
static defaultProps = {
btnname:'按钮的默认值'
}
render(){
return <div>
<button>{this.props.btnname}</button>
</div>
}
}
14.属性的类型校验
1.安装prop-types
//安装
npm i prop-types --dev
//引入
import PropTypes from 'prop-types';
class App extends React.Component{
render(){
return <div>
//btnname要求number类型,传入其他类型会抛异常
<Btn btnname='按钮的新值'/>
</div>
}
}
class Btn extends React.Component{
static propTypes = {
btnname:PropTypes.number
}
render(){
return <div>
<button>{this.props.btnname}</button>
</div>
}
}
15.事件
class App extends React.Component{
state = {
num:10
}
//事件函数 一定要写成箭头函数的形式
addNum = () => {
this.setState({
num:++this.state.num
});
}
render(){
return <div>
<button onClick={ this.addNum }>{ this.state.num }</button>
</div>
}
}
16.事件函数的this指向问题和解决方案
class App extends React.Component{
state = {
num:10
}
//当不使用箭头函数的时候,点击按钮this为undefined
addNum(){
this.setState({
num:++this.state.num
});
}
render(){
return <div>
<button onClick={ this.addNum }>{ this.state.num }</button>
</div>
}
}
tips:this的问题暂不清楚
this指向我查了一些资料,以下是比较认同的:
React事件处理函数中的this指向问题(undefined问题)_Actions speak louder than words-CSDN博客_react this undefined
这是第一个博客中的一段:
这不是React的原因,这是JavaScript中本来就有的。如果你传递一个函数名给一个变量,然后通过在变量后加括号()来调用这个方法,此时方法内部的this的指向就会丢失 ,也就是说在JavaScript中就有这么一个陷阱:
注意到现在没有直接调用obj对象中的testLog方法,而是使用了一个中间变量tmpLog过渡,当使用括号()调用该方法时,方法中的this丢失了指向,会指向window,进而window.tmp未定义就是undefined;
let obj = {
tmp:'Yes!',
testLog:function(){
console.log(this.tmp);
}
};
let tmpLog = obj.testLog;
tmpLog();
在React(或者说JSX)中,传递的事件参数不是一个字符串,而是一个实实在在的函数:
这样说,React中的事件名(eg:onClick、onChange)就是所举例子中的中间变量,React在事件发生时调用onClick,由于onClick只是中间变量,所以处理函数中的this指向会丢失,其实真正调用时并不是this.handleClick(),如果是这样调用那么this指向就不会有问题。真正调用时是onClick()。
为了解决这个问题,我们需要在实例化对象的时候,在构造函数中绑定this,使得无论事件处理函数如何传递,它的this的指向都是固定的,固定指向我们所实例化的对象。
解决this指向为undefined的四种方案:
(1). 在构造函数中使用bind绑定this
class Button extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(){
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
(2). 在调用的时候使用bind绑定this
class Button extends React.Component {
handleClick(){
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>
Click me
</button>
);
}
}
(3). 在调用的时候使用箭头函数绑定this
class Button extends React.Component {
handleClick(){
console.log('this is:', this);
}
render() {
return (
<button onClick={()=>this.handleClick()}>
Click me
</button>
);
}
}
(4). 使用属性初始化器语法绑定this(实验性)
静态方法,this指向当前实例
class Button extends React.Component {
handleClick=()=>{
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
四种方案之间的比较:
方式2和方式3都是在调用的时候再绑定this。
优点:写法比较简单,当组件中没有state的时候就不需要添加类构造函数来绑定this
缺点:每一次调用的时候都会生成一个新的方法实例,因此对性能有影响,并且当这个函数作为属性值传入低阶组件的时候,这些组件可能会进行额外的重新渲染,因为每一次都是新的方法实例作为的新的属性传递。
方式1在类构造函数中绑定this,调用的时候不需要再绑定
优点:只会生成一个方法实例,并且绑定一次之后如果多次用到这个方法也不需要再绑定。
缺点:即使不用到state,也需要添加类构造函数来绑定this,代码量多一点。
方式4:利用属性初始化语法,将方法初始化为箭头函数,因此在创建函数的时候就绑定了this。
优点:创建方法就绑定this,不需要在类构造函数中绑定,调用的时候不需要再作绑定。结合了方式1、方式2、方式3的优点
缺点:目前仍然是实验性语法,需要用babel转译
总结:
方式1是官方推荐的绑定方式,也是性能最好的方式。方式2和方式3会有性能影响并且当方法作为属性传递给子组件的时候会引起重渲问题。方式4目前属于实验性语法,但是是最好的绑定方式,需要结合bable转译。只要是需要在调用的地方传参,就必须在事件绑定的地方使用bind或者箭头函数,这个没有什么解决方案。
17.事件传递参数的方式
class App extends React.Component{
showMsg = (msg) = > {
console.log(this);
console.log(msg);
}
render(){
return (
<div>
//1.使用bind绑定this并传入参数
<button onClick={this.showMsg.bind(this,'A')}>按钮</button>
//2.在onClick使用箭头函数并在方法中传入参数
<button onClick={() => { this.showMsg('B') }}>按钮</button>
</div>
)
}
}
18.受控组件和非受控组件
常用于表单
1.受控组件
当组件的值被state控制的时候,该组件为受控组件。如下,input和checkbox的值被state控制
input:
1.使用defaultValue来为input框赋值
2.使用onChange方法 + value属性 来获取和赋值input框
class App extends React.Component{
state = {
msg:'123'
}
//这里已实现一个双向数据绑定
inputChange = (e) => {
console.dir(e.target);
const { value } = e.target;
this.setState({
msg: value
});
}
render(){
return (
<div>
<input type="text" defaultValue={this.state.msg} />
<input type="text" onChange={this.inputChange} value={this.state.msg} />
</div>
);
}
}
checkbox:
class App extends React.Component{
state = {
check:true
}
checkChange = (e) =>{
this.setState({
check:e.target.checked
});
}
render(){
return (
<div>
<input type="checkbox" onChange={ this.checkChange } checked={this.state.check} />
<h3>{ '' + this.state.check }</h3>
</div>
);
}
}
2.非受控组件
未被state控制的组件称之为非受控组件,对此react提供了操作dom的操作,在非受控组件上使用ref即可获得组件的值,如下面标题19
19.在react中操作dom元素
通过 React.createRef() 创建引用,在标签上使用ref属性和引用相关联,来操作dom元素
class App extends React.Component{
constructor(){
super();
//创建一个引用
this.inRef = React.createRef();
}
inputChange = () => {
console.log(this.inRef.current);
}
render(){
return (
<div onChange={this.inputChange}>
//让ref和dom元素产生联系
<input type="text" ref={this.inRef} />
</div>
);
}
}
20.组件传值
1.父传子
类组件
通过在父组件上自定义属性来传值给子组件
class App extends React.Component{
render(){
return <div>
<Btn btnname="按钮的名字"/>
</div>
}
}
class Btn extends React.Component{
render(){
return <div>
<button>{this.props.btnname}</button>
</div>
}
}
函数组件
通过在函数组件中定义props形参来获取props
class App extends React.Component{
render(){
return <div>
<Btn btnname="按钮的名字"/>
</div>
}
}
const Btn = (props) => {
render(){
return <div>
<button>{props.btnname}</button>
</div>
}
}
2.子传父
类组件
在父组件中定义一个方法,通过自定义属性将该方法传入;子组件通过this.props获取到该方法,子组件中一个方法用于触发this.props上的方法,子组件在this.props上的方法中传入参数,达到子向父传递参数的目的
class App extends React.Component{
state = {
num:10
}
add = (unit) => {
this.setState({
num: this.state.num + unit
});
}
render(){
return (
<div>
<h1>{this.state.num}</h1>
<div>
<AddBtn addUnit={ this.add } />
</div>
</div>
)
}
}
class AddBtn extends React.Component{
onClick = () => {
//通过props获取到父组件的函数并传入参数10
this.props.addUnit(10);
}
render(){
return (
<button onClick={this.onClick} >+10</button>
)
}
}
3.context跨级组件传值
使用getChildContext获取上下文进行跨级组件传值,接收组件通过static contextTypes对象来限制接收数据类型,使用this.context来获取值。
import React from 'react';
import PropTypes from 'prop-types';
// 这是通过组件嵌套组件,基于props的数据一层一层往里传值
// react提供context来解决组件间跨级传值
// 孙级组件
class Son extends React.Component {
// 3.限制传入的数据类型
static contextTypes = {
sum: PropTypes.number
};
render() {
return <div>
孙级组件接收最外层组件的传值{this.props.num}
<br />
context跨级孙级组件接收最外层组件的传值{this.context.sum}
</div>;
}
}
// 子级组件
class Child extends React.Component {
render() {
return <div>
<Son num={this.props.num} />
</div>;
}
}
// 最外层组件
class ContextLevel extends React.Component {
// 1.定义静态属性传值类型 上下文
static childContextTypes = {
sum: PropTypes.number
};
// 2.给上下文传入数据 不管孙组件或是子组件都可获取到该数据
getChildContext(){
return {
sum: 888
}
}
render() {
return <div>
<Child num={666} />
</div>;
}
}
export default ContextLevel;
运行示意:
函数组件
class App extends React.Component{
state = {
num:100
}
decrease = (unit) => {
this.setState({
num: this.state.num - unit
});
}
render(){
return (
<div>
<h1>{this.state.num}</h1>
<div>
<AddBtn decreaseUnit={ this.decrease } />
</div>
</div>
)
}
}
const DecreaseBtn = (props) => {
onClick = () => {
//通过props获取到父组件的函数并传入参数10
props.decreaseUnit(10);
}
render(){
return (
<button onClick={this.onClick} >-10</button>
)
}
}
//精简函数组件
const DecreaseBtn = (props) => <button onClick={() => {props.decreaseUnit(10)}} >-10</button>
21.生命周期
图示:React lifecycle methods diagram
常用生命周期:
展开不常用的声明周期后:
1.生命周期的三大阶段
挂载时
1.构造函数constructor
什么时候触发?实例创建时触发
有什么用?初始化数据 比方state或非受控表单
2.render
什么时候触发?构造函数执行完毕后 (当state或props发生改变时,render会重新执行)
有什么用?渲染视图
3.componentDidMount
什么时候触发?视图渲染完毕
有什么用?发送异步请求,类似于vue中的mounted
更新时
1.render
什么时候触发? 当state或props发生改变时,render会重新执行
2.componentDidUpdate
什么时候触发?组件更新完毕
用处? 很少用到 比方说获取变更后的数据或者dom元素
tips:该声明周期不适宜改动state,因为state会引发render,进入死循环
卸载时
1.componentWillUnmount
什么时候触发? 1.路由页面切换 2.条件渲染
有什么用? 1.清除定时器 2.解绑事件 3.取消订阅 4.取消一些异步任务
2.常见生命周期示例
import React, { Component } from 'react';
class App extends Component {
// state = {
// num: 100,
// show: true
// }
constructor() {
super();
// 初始化 state的数据
let time = Date.now();
time = time * 1000 - 100 + 10000;
this.state = {
time: time,
show: true
}
// 非受控表单
this.inpRef = React.createRef();
}
componentDidMount() {
// 组件挂载完毕
// console.log("componentDidMount");
}
componentWillUnmount() {
// 组件卸载时
}
componentDidUpdate() {
// 组件更新完毕时
// console.log("componentDidUpdate");
}
render() {
return (
<div>
{/* <button onClick={() => this.setState({ num: 10 })} >{this.state.num}</button> */}
<button onClick={() => this.setState({ show: !this.state.show })} >切换显示</button>
//react的条件渲染
{this.state.show && <Btn></Btn>}
</div>
);
}
}
let timeId = -1;
class Btn extends Component {
state = {
num: 0
}
componentDidMount() {
console.log("Btn 加载完毕");
timeId = setInterval(() => {
console.log("定时器执行");
this.setState({
num: this.state.num + 1
})
}, 1000);
}
componentWillUnmount() {
console.log("Btn 组件被卸载");
clearInterval(timeId);
}
render() {
// this.setState
return (
<div>
<hr />
<h1>{this.state.num}</h1>
<button>子组件</button>
<hr />
</div>
);
}
}
export default App;
tips:生命周期更新
生命周期具体执行示意图:
1.挂载阶段生命周期
挂载阶段先后经历constructor(构造器),UNSAFE_componentWillMount(即将挂载),render(渲染),componentDidMount(已挂载) 四个生命周期
import React from 'react';
class MountLife extends React.Component {
constructor(props){
super(props);
console.log('挂载阶段-1-constructor-构造器');
}
UNSAFE_componentWillMount(){
console.log('挂载阶段-2-componentWillMount-准备挂载');
}
render() {
console.log('挂载阶段-3-render-渲染');
return <div></div>;
}
componentDidMount(){
console.log('挂载阶段-4-componentDidMount-已挂载');
}
}
export default MountLife;
运行示意:
2.shouldComponentUpdate
该生命周期函数决定数据变化时组件是否要更新(渲染),它有两个形参:nextProps, nextState,此二个形参表示props和state每次改变后的新值.这表明该生命周期函数参与组件更新的两个过程,一是state更新,二是props更新。
示意图:
import React from 'react';
class CtrlUpdate extends React.Component {
state = {
msg: 'hello'
};
changeMsg = () => {
this.setState({
msg: 'hello'
},() => {
console.log('已更改后的数据:',this.state.msg);
});
}
render() {
return (
<>
<h2>{this.state.msg}</h2>
<button onClick={ this.changeMsg }>更新数据</button>
</>
);
}
// 该生命周期函数决定数据变化时组件是否要更新(渲染)
// nextProps代表更新后的props
// nextState代表更新后的state
shouldComponentUpdate(nextProps, nextState){
// 返回true时允许更新,反之,则禁止更新
// console.log('原值:', this.state.msg);
// console.log('修改后值:', nextState.msg);
// if(this.state.msg === nextState.msg){
// // 修改值相同则不更新组件
// console.log('不更新');
// return false
// }else{
// console.log('更新');
// return true;
// }
return this.state.msg !== nextState.msg;
}
}
export default CtrlUpdate;
3.更新时
更新阶段有两个,一是state变更,一是props变更
1.state更新阶段生命周期
先后经历shouldComponentUpdate(决定是否更新),componentWillUpdate,render,componentDidUpdate 四个生命周期
import React from 'react';
class StateUpdateLife extends React.Component {
state = {
msg: 'hello'
};
render() {
console.log('更新阶段-3-render-渲染');
return (
<>
<h2>{this.state.msg}</h2>
<button onClick={ () => this.setState({ msg: '你好' }) }>更新数据</button>
</>
);
}
shouldComponentUpdate(nextProps, nextState){
console.log('更新阶段-1-shouldComponentUpdate-决定是否可以更新');
return this.state.msg !== nextState.msg;
}
UNSAFE_componentWillUpdate(){
console.log('更新阶段-2-componentWillUpdate-组件即将更新');
}
componentDidUpdate(){
console.log('更新阶段-4-componentDidUpdate-组件已更新');
}
}
export default StateUpdateLife;
运行示意:
点击后
2.props更新阶段生命周期
先后经历componentWillReceiveProps(组件即将接收props),shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate 五个生命周期
import React from 'react';
class Child extends React.Component {
UNSAFE_componentWillReceiveProps(){
console.log('更新阶段-0-componentWillReceiveProps-组件即将接收props');
}
shouldComponentUpdate(nextProps, nextState){
console.log('更新阶段-1-shouldComponentUpdate-决定是否可以更新');
return this.props.msg !== nextProps.msg;
}
render() {
console.log('更新阶段-3-render-渲染');
return <div>{this.props.msg}</div>;
}
UNSAFE_componentWillUpdate(){
console.log('更新阶段-2-componentWillUpdate-组件即将更新');
}
componentDidUpdate(){
console.log('更新阶段-4-componentDidUpdate-组件已更新');
}
}
class PropsUpdateLife extends React.Component {
state = {
msg: 'hello'
};
render() {
return (
<>
<h2>{this.state.msg}</h2>
<button onClick={ () => this.setState({ msg: '你好' }) }>更新数据</button>
<Child msg={ this.state.msg } />
</>
);
}
}
export default PropsUpdateLife;
运行示意:
3.卸载阶段
一个生命周期:componentWillUnmount
import React from 'react';
class Unmount extends React.Component {
render() {
return <div>卸载阶段</div>;
}
componentWillUnmount(){
console.log('卸载阶段-componentWillUnmount-组件即将卸载');
}
}
export default Unmount;
22.shouldComponentUpdate
shouldComponentUpdate(){}:是否允许组件更新,默认为true
返回true,允许render被触发,可以更新;返回false,不允许render被触发,不可以更新
具有提高组件性能的作用
import React, { Component } from 'react';
class App extends Component {
state = {
time: 0
}
// 是否允许组件更新
// 如果 返回了 true render 就会被触发 允许 更新
// 如果 返回了 false render 就不会触发 不允许更新
shouldComponentUpdate() {
// console.log("shouldComponentUpdate");
// return false;
// return undefined == false
}
render() {
// console.log("render");
console.count("render");
return (
<div>
<button onClick={() => { this.setState({ time: Date.now() }) }} >
{this.state.time}</button>
</div>
);
}
}
export default App;
解决render频繁没有必要的渲染 示例:
import React, { Component } from 'react';
class App extends Component {
state = {
list: ["海带"],
text: "123"
}
// 输入框的值改变事件
onChange = (e) => {
this.setState({
text: e.target.value
})
}
// 点击添加
onClick = () => {
const { list, text } = this.state;
const set = new Set(list);
set.add(text);
this.setState({ list: [...set] });
}
render() {
const { text, list } = this.state;
return (
<div>
<div>
<input onChange={this.onChange} value={text} type="text" />
<button onClick={this.onClick} >添加</button>
</div>
<MyUl list={list}></MyUl>
</div>
);
}
}
class MyUl extends Component {
shouldComponentUpdate(nextProps, nextState) {
// nextProps.list 下一个 新的 数组
// this.props.list 上一个 旧的 数组
//判断上一个props的值与下一个props的值是否一致,来决定是否进行渲染
return nextProps.list !== this.props.list;
}
render() {
console.count("MyUl的render函数的调用次数")
return (
<ul>
{this.props.list.map((v, i) => <li
key={v}
>{v}</li>)}
</ul>
);
}
}
export default App;
23.高性能组件
1.类组件
引入PureComponent,并被类组件继承,即可使该类组件变为高性能组件
import React, { Component,PureComponent } from 'react';
class App extends Component {
state = {
list: ["海带"],
text: "123"
}
// 输入框的值改变事件
onChange = (e) => {
this.setState({
text: e.target.value
})
}
// 点击添加
onClick = () => {
const { list, text } = this.state;
const set = new Set(list);
set.add(text);
this.setState({ list: [...set] });
}
render() {
const { text, list } = this.state;
return (
<div>
<div>
<input onChange={this.onChange} value={text} type="text" />
<button onClick={this.onClick} >添加</button>
</div>
<MyUl list={list}></MyUl>
</div>
);
}
}
class MyUl extends PureComponent{
render() {
console.count("MyUl的render函数的调用次数")
return (
<ul>
{this.props.list.map((v, i) => <li
key={v}
>{v}</li>)}
</ul>
);
}
}
export default App;
2.函数组件
引入memo,并使其包裹住函数组件,即可实现高性能
import React, { Component,PureComponent,memo } from 'react';
class App extends Component {
state = {
list: ["海带"],
text: "123"
}
// 输入框的值改变事件
onChange = (e) => {
this.setState({
text: e.target.value
})
}
// 点击添加
onClick = () => {
const { list, text } = this.state;
const set = new Set(list);
set.add(text);
this.setState({ list: [...set] });
}
render() {
const { text, list } = this.state;
return (
<div>
<div>
<input onChange={this.onChange} value={text} type="text" />
<button onClick={this.onClick} >添加</button>
</div>
<MyUl list={list}></MyUl>
</div>
);
}
}
const MyUl = memo((props) => {
console.count("函数组件的render");
return (
<ul>
{props.list.map((v, i) => <li
key={v}
>{v}</li>)}
</ul>
);
});
export default App;
24.jsx中的标签和样式
1.jsx中特定的标签属性
//标签的class改为className,以防和类声明class冲突
<div className="col"></div>
//label标签for属性改为htmlFor
<label htmlFor="username"></label>
//富文本回显属性改为dangerouslySetInnerHTML={{__html:`富文本内容`}}
<div dangerouslySetInnerHTML={{__html:`<h1>哈哈</h1>`}}></div>
//行内样式的写法
<h1 style={{color:"red",backgroundColor:"yellow"}}>行内样式</h1>
2.样式引用
全局样式
只要在代码中这样引用,就一定是全局样式,不管在什么组件中:
//全局引用
import '../App.css';
局部样式
新建局部样式文件命名规则必须是:*.module.css ,引用时需要进行命名,否则还是全局样式
import SearchCss from './Search.module.css';
<input className={SearchCss.search} placeholder="搜索框" />
3.空标签
import React from 'react';
/**
* jsx必须要有根标签,可以是<></> 空标签,其缺点是空标签上不能添加属性
*/
class Empty extends React.Component {
render() {
return (
<>
<p>啦啦啦</p>
<div style={ mycolor }>嘿嘿嘿</div>
</>
);
}
}
// jsx中写{}代表里面需要写js,写()代表里面需要写html
// jsx中标签上style不能写字符串,必须以变量插入的形式存在
const mycolor = { color: 'pink' };
export default Empty;
25.react 路由
1. 安装sass(样式使用)
npm install sass-loader node-sass
2.在新建react项目基础安装react路由
npm install react-router-dom
3.react-router示例
1.Router 负责把所有路由相关的组件都包裹起来
2.Link 路由超链接标签
3.Route 显示路由页面
4.BrowserRouter 可以实现单页应用,url发生改变了,页面不会直接刷新;但必须要和后台结合使用,打包后需要做处理,不然无法使用
5.HashRouter 最推荐的方式 #后面的路径改变
import React from "react";
import { HashRouter as Router, Link, Route } from "react-router-dom";
export default function App() {
return (
<Router>
<nav>
<Link to="/" >首页</Link>
<Link to="/about" >关于</Link>
<Link to="/users" >用户</Link>
</nav>
<section>
<Route exact path="/" component={Home} />
<Route exact path="/about" component={About} />
<Route exact path="/users" component={Users} />
</section>
</Router>
);
}
function Home() {
return <h2>Home</h2>;
}
function About() {
return <h2>About</h2>;
}
function Users() {
return <h2>Users</h2>;
}
4.路由默认的匹配规则
react-router默认匹配规则是一种包含规则,而不是一种等于规则。
比如path="/about",则该path包含"/"和"/about"两种路由,则页面上会展示两种路由;而不是path="/" === path="/about"这种等于的规则。
在router标签上加上exact的属性,则会变成等于的规则,成为一种精确的路由匹配。
一旦加上路由精确匹配,就不能再实现路由嵌套
<Router exact path="/" component={Home}></Router>
5.路由参数
使用路由,组件上多了三个对象:
- history 负责编程式导航
- location 和原生location相似
- match 获取url的参数
下图使用this.props.match.params来获取url上携带的参数
<Route path="/user/:id" component={UserDetail} ></Route>
// 用户详情页面
class UserDetail extends Component {
render() {
// console.log(this.props.match.params);
console.log(this.props);
return (
<h1>用户的详情页面 {this.props.match.params.id} </h1>
);
}
}
编程式导航
this.props.history.push('/user/' + id);
6.路由重定向
Redirect标签用于路由重定向,但是路由默认匹配规则并不能让Redirect起作用,该路由代码不加Switch标签时,路由默认走向为if(),if()并行,但我们需要让路由if()elseif()else()走向,所以提供Switch标签满足这一走向,Switch标签常与Redirect标签一起出现,配合使用
const App = () => {
return <div>
<Router>
<nav>
<Link to="/" >首页</Link>
<Link to="/user" >用户</Link>
<Link to="/about" >关于</Link>
</nav>
<section>
<Switch>
<Route exact path="/" component={Home} ></Route>
<Route exact path="/user" component={User} ></Route>
<Route path="/about" component={About} ></Route>
<Route path="/404" component={PageNotFound} ></Route>
{/* 重定向 */}
<Redirect to="/404" />
</Switch>
</section>
</Router>
</div>
}
26.redux
和vuex一样,全局数据管理仓库,集中式管理数据的思想。
1.redux和react-redux
redux是一个完全独立的第三方库,可以使用在任意框架中,非react特供;而react-redux,是一个连接react和redux的库
2.redux操作
//引入创建store的方法
import { createStore } from 'redux'
function counter(state = 100, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'decrement':
return state - 1
default:
return state
}
}
//创建store
let store = createStore(counter);
//导出 store
export default store;
1.获取数据
import store from './store';
//获取仓库
store.getStore();
//获取仓库中状态
store.getState();
2.修改数据
//dispatch 通知 INCREMENT判断+1
store.dispatch({ type:'INCREMENT' });
3.开启订阅
开启订阅需要考虑放在哪里合适,订阅只需要开启一次
挂载时适合方订阅,订阅中更新state中的数据即可
//仓库中的数据发生改变后通知更新
componentDidMount(){
//这里挂载订阅的函数,用于取消订阅
this.unsubscribe = store.subscribe(() => {
this.setState({
num: store.getState();
});
});
}
4.取消订阅
componentWillUnmount(){
//卸载组件时直接调用取消订阅的函数即可
this.unsubscribe();
}
5.redux工作流
1.store:指的是store/index.js代码
2.React Component:引用了仓库数据的标签
3.Reducer:负责接收通知,亲自修改数据
4.Action:用户点击按钮的行为或变量,触发了修改仓库中数据
27.react-redux
一个库,特供react
1.安装
npm i redux react-redux
2.react index中需要配置
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// 1 引入仓库
import store from "./store";
// 2 引入 负责把 store 和 App 联系起来 Provider组件
import { Provider } from "react-redux";
ReactDOM.render(
// 3 把 store 和 App 联系起来
<Provider store={store} >
<App />
</Provider>,
document.getElementById('root')
);
3.react App中需要配置
import React, { Component } from 'react';
//引用该函数负责接收全局数据
import { connect } from "react-redux";
class App extends Component {
render(){
console.log(this.props);
return (
<div>
<h1>App</h1>
<h2>{ this.props.a }</h2>
<div>
<button onClick={this.props.addNum}>+</button>
</div>
</div>
)
}
}
//定义组件属性props与state映射关系的对象
const mapStateToProps = (state) => {
return {
a: state
}
}
//修改state中的数据 将props中的方法 直接触发到store的reducer上
const mapDispatchToProps = (dispatch) => {
return {
//this.props.addNum()
addNum(){
dispatch({ type:'INCREMENT' });
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(App);
4.拆分Reducer
示例:
store--|
index.js
reducer--|
index.js
cartReducer.js
goodsReducer .js
index.js 总管理员,内部需要合并其他reducer
// 1 引入其他的小管理员
import cartReducer from "./cartReducer";
import goodsReducer from "./goodsReducer";
// 2 引入一个负责合并管理员的 函数
import {combineReducers } from "redux";
// 3 合并并导出
export default combineReducers({cartReducer,goodsReducer});
cartReducer和cartReducer 小管理员
//购物车管理员
const defaultState = {
num: 1000
}
export default (state = defaultState,action) => {
return state;
}
//商品管理员
const defaultState = {
num: 10000
}
export default (state = defaultState,action) => {
return state;
}
获取仓库中的数据
const mapStateToProps = (state) => {
return {
// 获取商品的数量
goodsNum: state.goodsReducer.num,
// 获取购物车的数量
cartNum: state.cartReducer.num
}
}
5.拆分action
示例:
store--|
actionCreator--|
index.js
其实就是把对象封装成一个函数,在App.js中引入来使用。
actionCreator中index.js内容:
// 负责创建 action 对象
export const addGoodsNumAction = () => {
return { type: "ADDGOODSNUM", unit: 1 }
}
// 初始化商品数据
export const initGoodsNumAction = (num) => {
return { type: "INITGOODSNUM", value: num }
}
App.js中引入
// 引入action的创建函数
import { addGoodsNumAction ,initGoodsNumAction} from "./store/actionCreator";
组件中使用
dispatch(addGoodsNumAction())
更新
1.Fragment
import React, { Fragment } from 'react';
/**
* Fragment:
* 1.和空标签一样,不会渲染为实体标签
* 2.只允许两个属性存在:key、children
*/
class FragmentTest extends React.Component {
state = {
arr: [1,2,3,4]
};
render() {
return (
<div>
{
this.state.arr.map((v, index) =>
<Fragment key={index}>
<h2>我是标题{index}</h2>
<p>我是段落{index}</p>
</Fragment>
)
}
</div>
)
}
}
export default FragmentTest;
2.lazyload(懒加载)和Suspense
import React, { Component, Suspense } from 'react'
// lazyLoad需要和Suspense搭配使用
// React有组件懒加载的功能
// Suspense 用于解决网络IO问题,用来包裹异步组件,添加loading效果等
// import Son from './Son';
// 使用懒加载
// 使用React.lazy()引入
const Son = React.lazy(() => import('./Son'));
export class Father extends Component {
render() {å
return (
<div>
{/* fallback:用来呈现组件还未渲染出来的UI或提示 */}
<Suspense fallback={ <h2>加载中。。。</h2> }>
<Son />
</Suspense>
</div>
)
}
}
export default Father
3.错误边界
在代码出错时显示设置的错误UI
无法在以下场景中捕获异常:
- 事件处理
- 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
- 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
错误边界组件只适用于类组件,通过两种方法来构建:
- componentDidCatch(error, errorInfo) 生命周期
- static getDerivedStateFromError(error) 静态方法
import React, { Component } from 'react'
/**
* 错误边界组件
* 这里使用componentDidCatch来构建
*/
export default class ErrorBoundary extends Component {
state = {
hasError: false
}
// 捕获错误
// componentDidCatch
// componentDidCatch(error, errorInfo){
// this.setState({
// error,
// errorInfo,
// hasError: true
// })
// }
// static getDerivedStateFromError()
// 获得从错误边界中产生的状态
static getDerivedStateFromError(error){
return {
hasError: true
}
// 这里最好写为: return {hasError: error}
// 本质上是根据error是否存在来判断是否设置 hasError 为 true,但其实能够进入这个函数的话,也就代表一定会有错误,因此直接设置 hasError 为 true 也未尝不可
}
/*
* JS中的&&符号:
1、运算方法:
只要“&&”前面是false,无论“&&”后面是true还是false,结果都将返“&&”前面的值;
只要“&&”前面是true,无论“&&”后面是true还是false,结果都将返“&&”后面的值;
2、总结:假前真后
* componentStack会告知你是该错误是在组件哪个地方失效!
*/
render() {
// 出错时展示出错的UI
if(this.state.hasError){
return (
<div>
{/*
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo.componentStack}
*/}
<h2>出错了!!</h2>
</div>
)
}else{
return this.props.children
}
}
}
在index.js中使用:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
// 错误边界组件 只适用于类组件
import ErrorBoundary from './components/error/ErrorBoundary';
ReactDOM.render(
// 包裹根组件使用
<ErrorBoundary>
<App />
</ErrorBoundary>,
document.getElementById('root')
);
4.HOC高阶组件
其实就是代码封装
如下:原先Son1和Son2中重复代码封装进HocTest函数,该函数返回一个无名类
import React, { Component } from 'react'
/**
* react中重点
* 高阶组件(HOC): 函数里面返回组件
* 一种基于 React 的组合特性而形成的设计模式
* 可以简单将其理解成 “类工厂”,一般在class组件时使用
*/
export class Hoc extends Component {
render() {
return (
<div>
<MySon1 />
<MySon2 />
</div>
)
}
}
export default Hoc;
// 额外:高阶函数(HOF):函数立main返回函数
class Son1 extends Component{
// state = {
// num: 2
// };
// componentDidMount(){
// setInterval(() => {
// this.setState({
// num: this.state.num + 2
// });
// },2000);
// }
render(){
return (
<h2>数字一:{this.props.num}</h2>
)
}
}
class Son2 extends Component{
// state = {
// num: 1
// };
// componentDidMount(){
// setInterval(() => {
// this.setState({
// num: this.state.num + 1
// });
// },1000);
// }
render(){
return (
<h2>数字一:{this.props.num}</h2>
)
}
}
/**
* 高阶组件
* 这里把类封装到一个函数中并返回出来
* 高阶组件中封装的类不需要名字
* 重复代码封装为高阶组件
* @param {*} comp 组件
* @param {*} num
* @param {*} time
* @returns
*/
function HocTest(Comp, result, time){
return class extends Component{
state = {
num: result
}
componentDidMount(){
setInterval(() => {
this.setState({
num: this.state.num + result
});
}, time);
}
render(){
return <Comp num={this.state.num} />
}
}
}
// 使用 HocTest 构建组件
const MySon1 = HocTest(Son1, 1, 1000);
const MySon2 = HocTest(Son2, 2, 2000);
该博客初版学习与黑马某课程,更新版本学习于(该老师讲得太棒了):【最新】零基础快速入门React 17.x_哔哩哔哩_bilibili