setState
- setState引起的react的状态是异步的。操作完毕setState之后如果直接取值,可能取不到最新的值,我们举个例子
console.log(this.state.num)
打印的值,总是上一次的值而不是最新的。
import React, {Component} from 'react';
class App extends Component {
state = {
num: 0
}
Add = () => {
this.setState({
num: this.state.num + 1
})
console.log(this.state.num);
}
render() {
return (
<div>
<li>num:{this.state.num}</li>
<button onClick={this.Add}>+ 1</button>
</div>
);
}
}
export default App;
那么我怎么得到最新的值呢,其实在setstate接收第二个参数,是一个回调函数,我们可以这样写
Add = () => {this.setState({num: this.state.num + 1},()=>console.log(this.state.num))}
- 在上面例子中,setstate 接收到的第一个参数是一个对象,其实也可以是一个stateChange对象的函数,这个对象函数呢,我们可以读取到state和props。我们来看一下
Add = () =>{
this.setState(
(state,props)=>{
console.log(this.state);
console.log(props);
return {
num: state.num+1
}
},
() => {
console.log(this.state);
}
)
}
路由组件的懒加载
我们知道,一个路由对应一个组件,但是如果在一个页面里面有100个路由,难道我页面渲染的时候要渲染100个组件吗?这明显是不合理的,我们需要的是用到哪个组件,显示哪个组件,比如我们来看下面这段代码
import React, {Component,Suspense, lazy} from 'react';
import {Link,Routes,Route} from "react-router-dom";
//如果这样写,组件会在第一次请求的时候就全部加载出来
// import A from "./components/a";
import Load from "./components/load";
//这样写,组件只有需要用到的时候才加载一次
const A=lazy(()=> {
return import("./components/a")
});
const B=lazy(()=>{
return import("./components/b")
});
class App extends Component {
render() {
return (
<div>
<ul>
<li><Link to="/a">我是a</Link></li>
<li><Link to="/b">我是b</Link></li>
</ul>
<div>
{/*Suspense标签包裹路由组件,当路由组件调用超时,则展示Suspense的fallback组件*/}
<Suspense fallback={<Load />}>
<Routes>
<Route path="/a" element={<A />} />
<Route path="/b" element={<B />} />
</Routes>
</Suspense>
</div>
</div>
);
}
}
export default App;
hooks
之前我说过,在function里面,是没有this的,那这也意味着我们用不了this.state this.props,this.ref,但是在react16.8以后,hooks函数的出现实现了在函数内使用这些功能,那么没有this我们怎么操作呢,没有this,但是我们有react啊,react提供api给函数用就可以了
React.useState(state-hooks)
看名字就知道了,这个hooks我们可以用来操作state,我们举个例子
import React from 'react';
function App(){
//定义count初始值
const initCountValue=0
//使用useState定义state和function,这个function不能直接调用,而是要在函数内部调用
const [count,setCount]=React.useState(initCountValue)
const [age,setAge]=React.useState(initCountValue)
//定义一个函数调用setCount
function add(){
//写法一 接收一个状态值
setCount(count+1)
}
function addAge(){
//写法二 接收原状态的值,返回新状态的值
setAge((age)=>age+1)
}
return (
<div>
<li>count:{count}</li>
<button onClick={add}>+1</button>
<li>age:{age}</li>
<button onClick={addAge}>+1</button>
</div>
)
}
export default App;
React.useEffect(effect-hooks)
- 我们知道,一个类是有生命周期函数的,那么在function中,我们怎么使用这些函数呢?
React.useEffect
就是监听函数声明周期的api,它接收三个参数,第一个参数就是收到监听后可以执行的动作,第二个参数相当于ComponentWillUnmount,第三个参数用来过滤监听哪些事件,默认监听所有,如果是空数组,默认是componentdidMount,如果是监听某个状态改变,我们就把哪个状态放到数组中即可
import React from 'react';
function App(){
//定义count初始值
const initCountValue=0
//使用useState定义state和function,这个function不能直接调用,而是要在函数内部调用
const [count,setCount]=React.useState(initCountValue)
const [age,setAge]=React.useState(initCountValue)
//定义一个函数调用setCount
function add(){
//写法一 接收一个状态值
setCount(count+1)
}
function addAge(){
//写法二 接收原状态的值,返回新状态的值
setAge((age)=>age+1)
}
React.useEffect(()=>{
const timer=setInterval(()=>{
//定义动作 执行+1操作
setCount(count+1)
},1000);
//卸载前执行操作
return ()=>{clearInterval(timer)}
//监听Age更新才执行 age 增加一次,count执行一次+1动作
},[age])
return (
<div>
<li>count:{count}</li>
<button onClick={add}>+1</button>
<li>age:{age}</li>
<button onClick={addAge}>+1</button>
</div>
)
}
export default App;
React.useRef(ref-hooks)
我们知道,在类里面我们使用ref1 = React.createRef();
这种方式来定义ref,那么在function中,我们怎么定义ref呢,我们看一下
import React from 'react';
function App(){
//定义ref
const ref1=React.createRef();
function getRef(){
//获取ref的值
console.log(ref1.current.value);
}
return (
<div>
<li><input type="text" ref={ref1}/></li>
<button onClick={getRef}>getRef</button>
</div>
)
}
export default App;
Fragment
不知道大家有没有发现,当我们使用return的时候,必须返回一个标签,所有的jsx标签必须被一个标签包裹,像这种
return (
<div>
<li><input type="text" ref={ref1}/></li>
<button onClick={getRef}>getRef</button>
</div>
)
那么我有一个想法,我如果想让我的jsx标签不被包裹,应该怎么办?我觉得最简单的方法就是把标签去掉,比如这样
return (
<>
<li><input type="text" ref={ref1}/></li>
<button onClick={getRef}>getRef</button>
</>
)
但是空标签有一个问题,我们没办法给它赋值key,比如我们map遍历的时候,这个时候Fragment作用就出来了,它是一个可以被赋值key的空标签
import React, {Fragment} from 'react';
function App(){
//定义ref
const ref1=React.createRef();
function getRef(){
//获取ref的值
console.log(ref1.current.value);
}
return (
<Fragment key="1">
<li><input type="text" ref={ref1}/></li>
<button onClick={getRef}>getRef</button>
</Fragment>
)
}
export default App;
context
一般后端人员对context还是很熟悉的,一个上下文,贯穿整个请求,从进程,到各个子进程,都可能用到context的东西,那么前端,也提出了一个context的概念,用来实现组件和子组件的数据传递,我们举个例子
在组件中使用context
import React from 'react';
//定义全局的context对象
const MyContext=React.createContext();
//定义provider标签 类组件用
const Provider=MyContext.Provider;
//定义consumer标签 函数组件用
const Consumer=MyContext.Consumer;
//这是一个父组件
class App extends React.Component {
//定义状态
state = {
name: 'React'
}
render() {
return (
<div className="App" style={{padding: '10px',border: '1px solid red'}}>
<h1>{this.state.name}</h1>
{/*父组件想要把上下文传递给子组件,必须使用context的Provider标签包裹,并把相关的值传递过去*/}
<Provider value={this.state}>
<span><B></B></span>
</Provider>
</div>
);
}
}
class B extends React.Component {
render() {
return (
<div className="App" style={{padding: '10px',border: '1px solid red'}}>
<h1>我是B</h1>
<span ><C></C></span>
</div>
)
}
}
class C extends React.Component {
//谁想从全局context拿到 谁声明contexttype
static contextType=MyContext;
render() {
return (
<div className="App" style={{padding: '10px',border: '1px solid red'}}>
<h1>我是C</h1>
{/*这里取值渲染*/}
<h1>App组件穿过来的值是{this.context.name}</h1>
<D></D>
</div>
)
}
}
function D() {
return (
<div className="App" style={{padding: '10px',border: '1px solid red'}}>
<h1>我是Func D</h1>
<Consumer>
{value=>{
return <h1>App组件穿过来的值是{value.name}</h1>
}}
</Consumer>
</div>
);
}
export default App;
purgecomponent
我们之前使用redux的时候说过,redux只负责存储数据,如果我们想重新render,那么就得执行一个命令this.setstate={}
,相当于告诉react,说我更新了state,你需要重新render。其实呢,我们啥也没更新。既然我们啥都没更新,为什么还要执行render?我们能不能等到真正的state发生变化的时候再render?答案是可以,因为我们知道有一个生命周期函数,叫getSnapshotBeforeUpdate
,我们可以通过对比state和props来判断返回true还是false,我们知道,既然getSnapshotBeforeUpdate是组件的生命周期函数,那么能不能让getSnapshotBeforeUpdate自动判断,而不是每次都是我手写判断呢?答案是可以,它是对component的优化,名字是React.PureComponent
,我们来看看使用
import React from 'react';
//这是一个父组件
class App extends React.PureComponent {
//定义状态
state = {
name: 'React'
}
render() {
// 初始化render之后,由于数据不在变化,也不在重新render
console.log('app.render');
return (
<div className="App" style={{padding: '10px',border: '1px solid red'}}>
<h1>{this.state.name}</h1>
<button onClick={() => this.setState({name: 'vue'})}>更改状态名字</button>
<B></B>
</div>
);
}
}
class B extends React.PureComponent {
// 初始化render一次之后,由于后续没有数据变化,故不再render
render() {
console.log('b.render');
return (
<div >
<h1>我是B</h1>
<span ></span>
</div>
)
}
}
export default App;
children props
- 一般我们写父子组件,可以通过嵌套来写
import React from 'react';
import "./App.css"
//这是一个父组件
class App extends React.PureComponent {
//定义状态
state = {
name: 'React'
}
render() {
return (
<div className="component">
<h1>我是App</h1>
{/*标签包裹的内容通过props.children获取*/}
<B x={100}>11211
<C></C>
</B>
</div>
)
}
}
class B extends React.PureComponent {
// 初始化render一次之后,由于后续没有数据变化,故不再render
render() {
console.log(this.props);
return (
<div className="component">
<h1>我是B</h1>
{/*获取组件内容*/}
<h1>组件内容:{this.props.children}</h1>
</div>
)
}
}
class C extends React.PureComponent {
render() {
return (
<div className="component">
<h1>我是C组件:{this.props.name}</h1>
</div>
)
}
}
export default App;
render props
- 但是在实际开发中,可能多个人写多个组件,我们不可能实现这种链式嵌套,因为也可能会有这样的情况
render() {
return (
<div>
<h1>我是App</h1>
{/*app组件内,需要B和C的组合来实现功能*/}
<B>
<C></C>
</B>
</div>
)
}
那么这种情况我们应该怎么传递数据呢?这里,我们就用到了render props
import React from 'react';
import "./App.css"
//这是一个父组件
class App extends React.PureComponent {
//定义状态
state = {
name: 'React'
}
render() {
// name = this.state.name;
return (
<div className="component">
<h1>我是App</h1>
{/*在App组件内 使用B组件嵌套c组件 并指定render接收的参数*/}
<B render={data=><C name={data} />} />
</div>
)
}
}
class B extends React.PureComponent {
// 初始化render一次之后,由于后续没有数据变化,故不再render
render() {
// console.log(this.props);
return (
<div className="component">
<h1>我是B</h1>
{/*获取组件内容*/}
<h1>组件内容:{this.props.children}</h1>
{/*指定给c组件传递的参数*/}
{this.props.render("22233")}
</div>
)
}
}
class C extends React.PureComponent {
render() {
console.log(this.props);
return (
<div className="component">
{/*打印b组件传递的参数*/}
<h1>我是C组件:{this.props.name}</h1>
</div>
)
}
}
export default App;
错误边界
我们知道,一个页面可能是由很多个组件组成的,但是在开发的时候我们也发现了,一旦一个组件出现问题,整个页面都会报错,我们举个例子
import React from 'react';
import "./App.css"
//这是一个父组件
class App extends React.PureComponent {
// state=[
// {name:'张三'},
// {name:'李四'},
// ]
//将state改成字符串
state = "aaaa"
render() {
// name = this.state.name;
return (
<div className="component">
<h1>我是App</h1>
{/*在App组件内 使用B组件嵌套c组件 并指定render接收的参数*/}
<B>{this.state}</B>
</div>
)
}
}
class B extends React.PureComponent {
render() {
const persons = this.props.children;
return (
<div>
<h2>我是b组件</h2>
{persons.map((item,index)=>(<li key={index}>{item.name}</li>))}
</div>
)
}
}
export default App;
那么问题来了,一个组件的错误,导致整个页面崩溃,这明显是不科学的。我们能不能有机制去捕捉组件的错误并做出对应的处理呢?就像try catch 这种模式,答案是有的。有两个方式来捕捉错误,getDerivedStateFromError
捕捉错误通知react的组件,根据错误标识做出下一步处理。componentDidCatch
可以做错误统计或者通知服务端错误详情
import React from 'react';
import "./App.css"
//这是一个父组件
class App extends React.PureComponent {
// state=[
// {name:'张三'},
// {name:'李四'},
// ]
//将state改成字符串
state = "aaaa"
//捕捉子组件的错误信息 ,给state标识错误
static getDerivedStateFromError(error) {
return {
hasError: true
}
}
//用于通知服务端错误信息或者统计错误
componentDidCatch(error, info) {
//捕捉错误
console.log(error, info)
}
render() {
//获取到state的错误标识,做出对应处理
if (this.state.hasError) {
return <h1>出错了,稍后再试</h1>
} else {
return (
<div className="component">
<h1>我是App</h1>
<B>{this.state}</B>
</div>
)
}
}
}
class B extends React.PureComponent {
render() {
const persons = this.props.children;
return (
<div>
<h2>我是b组件</h2>
{persons.map((item, index) => (<li key={index}>{item.name}</li>))}
</div>
)
}
}
export default App;
组件通讯方式总结
- props 用于父子组件传递数据
- 消息订阅-发布 可以用于兄弟组件,祖孙组件
- redux 用于兄弟组件,祖孙组件
- context 用于祖孙组件
react route6
推荐使用函数式组件
结尾
至此,react基本就学完了。我们可以跟前端同学要个项目研究研究,自己练一练。祝大家学习愉快!