react基础05-setState的两种方式以及回调函数、路由组件懒加载、hooks、Fragment、context、shouldComponentUpdate和PureComponent进行组件优化、children props和render props、错误边界、react组件通信总结
setState()
1、setState有两种写法
第一种:对象形式
this.setState({ count: count + 1 }, () => console.log(this.state.count))
第二种:函数形式,返回一个对象。函数的第一个参数是state,第二个参数是props
this.setState( (state, props) => ({ count: state.count + 1 }), () => console.log(this.state.count) )
2、setState还有第二个参数,callback,是一个回调函数,可选,它在状态更新完后、视图也更新(render)完后才被调用
如果需要在setState执行后获取最新的状态,就在callback中获取,有点像this.$nextTick()
路由组件懒加载
1、通过lazy方法和import动态加载路由组件
2、通过<Suspense></Suspense>指定在加载得到路由打包文件前显示一个loading界面
import React, { Component, lazy, Suspense } from 'react'
import { NavLink, Route, Switch, Redirect } from 'react-router-dom'
import Loading from './Loading'
// import About from './About'
// import Home from './Home'
// import Home1 from './Home1'
const About = lazy(() => import('./About'))
const Home = lazy(() => import('./Home'))
const Home1 = lazy(() => import('./Home1'))
export default class LazyLoad extends Component {
render() {
return (
<>
<NavLink to="/about" tag="p">
About
</NavLink>
<NavLink to="/home">Home</NavLink>
<Suspense fallback={<Loading />}>
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Route path="/home" component={Home1} />
</Switch>
<Redirect from="/" to="/about" />
</Suspense>
<style>
{`
.active {
background-color: greenyellow;
}
`}
</style>
</>
)
}
}
Hooks
Hook是react16.8.0新增的语法,可以在函数式组件中使用state及其他的react特性
三个常用的hook
1、useState
const [count, setCount] = useState(0)
第一次初始化指定的值会在内部做缓存
setCount有两种写法:
setCount(count + 1) // 或 setCount(count => count + 1)
2、useEffect
useEffect(() => { const timer = setInterval(() => { setCount(count => count + 1) }, 1000) // 返回的函数相当于componentWillUnmount钩子,组件卸载前执行,在这里清除定时器、取消订阅等 return () => { clearInterval(timer) } }, [])
(1)第一个参数:回调函数,可以执行ajax请求,开启定时器等操作
(2)第二个参数:
第一种情况:不传参,执行1+n次,初始化调用一次,监听所有的state,有任何一个发生改变,都会再次执行一次(有点像render(1+n);也有人说像componentDidUpdate,但是这个钩子初始化不执行,状态发生变化时才会执行(n)) 不传参的情况在实际开发中较少见,要么传个[],要么传个数组里面监听状态
第二种情况:传 [],不监测任何状态,回调函数只会在第一次render后执行,相当于componentDidMount钩子,可以在这里进行ajax请求、开启定时器、订阅消息
第三种情况:传 [count, name],初始化执行一次,并且监听count和name的状态,如果发生改变,执行回调函数(有点像vue中的computed)
(3)返回值:返回一个函数,该函数相当于componentWillUnmount钩子,组件卸载前执行,在这里清除定时器、取消订阅等
3、useRef
const inputRef = useRef() return ( <div> <input type="text" ref={inputRef} /><button onClick={() => console.log(inputRef.current.value)}> 点击打印input框内容 </button> <button onClick={() => (inputRef.current.value = '123')}> 设置input框内容 </button> </div> )
Ref Hook可以在函数式组件中存储/查找组件内的标签或其他数据。功能和类式组件中的createRef()一样
Fragment标签
<Fragment key={1}></Fragment>和<></> 都是不渲染出真实的根标签,减少一层dom结构,为了满足jsx的语法规范
Fragment标签只能接收一个参数key,如果当前组件不参与遍历,使用空标签即可,如果参与遍历,可以使用Fragment标签,传入key值
context
概念:
用于【祖组件】和【后代组件】间通信,类似于vue中的provide / inject
实际开发中一般不用context,使用react-redux中的Provider组件,在入口文件中将App组件包裹起来可以将state传到每个组件中
ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.querySelector('#root') )
使用:
1、通过Provider的value属性传递数据
2、类组件可以通过static关键字声明接收context,也可以通过Consumer组件获取context
3、函数式组件可以通过useContext钩子接收context,也可以通过Consumer组件去获取context
import React, { Component } from 'react'
const Context = React.createContext()
const { Provider, Consumer } = Context
export default class A extends Component {
state = { name: '小明', age: 18 }
render() {
const { name, age } = this.state
return (
<div className="box a">
<h2>A组件</h2>
<p>用户名:{name}</p>
{/* 1、通过Provider包裹子组件,子组件及子组件中的组件都具有context */}
<Provider value={{ name, age }}>
<B />
</Provider>
<style>
{`
.box{padding:8px;}
.a {background-color: greenyellow;}
.b {background-color: yellowgreen;}
.c {background-color: deeppink;}
.d {background-color: pink;}
`}
</style>
</div>
)
}
}
class B extends Component {
render() {
return (
<div className="box b">
<h3>B组件</h3>
<C />
</div>
)
}
}
class C extends Component {
static contextType = Context // 2、类式组件的context中如果需要有值,必须写上context声明
render() {
return (
<div className="box c">
<h4>C组件</h4>
<p>类式组件接收用户名(第一种方式):{this.context.name}</p>
<p>
<Consumer>
{({ name }) => `类式组件接收用户名(第二种方式):${name}`}
</Consumer>
</p>
<D />
</div>
)
}
}
function D() {
return (
<div className="d">
<h5>D组件</h5>
<p>
{/* 3、函数式通过Consumer组件来获取context */}
<Consumer>{({ name }) => `函数式组件接收用户名:${name}`}</Consumer>
</p>
</div>
)
}
效果:

组件优化(解决render无效调用)
Component的2个问题:
1、只要执行setState(),即使不改变数据,组件也会重新render,导致效率低
2、只要当前组件重新render,就会触发子组件重新render,即使子组件中没有使用父组件的任何数据,这也会导致效率低
期望的做法:
只有当组件的state或props发生变化时才调用render
原因:
shouldComponentUpdate钩子默认返回true,这导致this.setState({})会调用render函数,但是这啥也没改,调用render就是浪费性能
解决:
第一种:
// 重写shouldComponentUpdate钩子,比较新旧state或props,有变化返回true,没有返回false。这样写的好处:this.setState({})将不会触发render shouldComponentUpdate(nextProps, nextState) { // 该钩子是在render前调用的,但是可以拿到render后的props和state,所以参数名叫nextProps、nextState return this.state.count !== nextState.count }
缺点:当state和props是多个时,需要写的判断太多了
第二种:
使用PureComponent替代Component,PureComponent重写了shouldComponentUpdate钩子,只有当state或props数据有变化才会触发render
注意:不要直接对state进行修改,如下代码react不支持
<button onClick={() => { // this.setState({ count: count + 1 }) const state1 = this.state // 浅复制会导致 // const state1 = JSON.parse(JSON.stringify(this.state)) state1.count += 1 this.setState(state1) }} > 点击+1 </button>
为什么:这里要求回调函数是一个纯函数。如果对this.state进行深复制就可以这么用,新对象和state间就没有关系了
特别注意纯函数中容易踩得坑:使用数组的push、unshift、splice等方法,这都是直接改变数组的,然后this.setState(arr),这不是改了原来的state了么
render props
如何将组件标签中的内容展示出来?
vue:vue插槽
react:
第一种:children props(标签中的内容是一个特殊的标签属性(props传参方式就是写标签属性),属性名叫children) 缺点:如果B组件需要A组件内的数据,做不到
import React, { PureComponent } from 'react'
import SetState from '../01setState'
export default class index extends PureComponent {
render() {
return (
<>
<h2>父组件</h2><A>
<B name={'props小明'} />
</A>
</>
)
}
}
class A extends PureComponent {
state = { name: '小明', age: 18 }
render() {
return (
<>
<h3>A组件</h3>
{this.props.children}</>
)
}
}
class B extends PureComponent {
render() {
return (
<>
<h3>B组件</h3>
<p>接收到props的name:{this.props.name}</p>
</>
)
}
}
第二种:render props(类似vue的slot) A组件中通过标签属性render(可以自由命名,但一般都是命名为render),值是一个函数,返回B组件,函数的参数就是A组件中调用render时传的值。
import React, { PureComponent } from 'react'
import SetState from '../01setState'
export default class index extends PureComponent {
render() {
return (
<>
<h2>父组件</h2>
<A render={values => <B name={values.name} />} /></>
)
}
}
class A extends PureComponent {
state = { name: '小明', age: 18 }
render() {
return (
<>
<h3>A组件</h3>
{this.props.render(this.state)}
</>
)
}
}
class B extends PureComponent {
render() {
return (
<>
<h3>B组件</h3>
<p>接收到A组件的name:{this.props.name}</p>
</>
)
}
}
效果:

错误边界
组件中的子组件和后代组件,一旦发生错误会导致当前组件的渲染被阻塞,错误边界就是让拦截错误的扩散,哪个子组件出错了就展示备用文案或组件,从而不影响到自身的渲染。
在当前组件中使用getDerivedStateFromError生命周期拦截错误,并且只能捕获子组件/后代组件生命周期(主要是应用于render)产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
import React, { Component } from 'react'
import Child from './Child'
export default class Parent extends Component {
state = { hasError: '', name: '小明' }
// 当子组件或后代组件发生错误时触发
static getDerivedStateFromError(err) {
console.log(err)
return { hasError: err } // render之前触发,需要返回一个对象,对state中的hasError重新赋值
}
componentDidCatch(error, info) {
console.log('统计错误,反馈给服务器')
}
render() {
console.log(this)
return (
<>
<h2>Parent组件</h2>
{this.state.hasError ? 'Child组件中有错误' : <Child />}
<h3>名字:{this.state.name}</h3>
</>
)
}
}
组件间通信方式总结
1、props:
children props
render props(相当于children props的升级,类似vue的slot)
2、消息订阅-发布:
pubsub-js、event等
3、集中式管理:
redux、dva等
4、context:
生产者-消费者模式
搭配方式:
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件:消息订阅-发布、集中式管理、context(多用于插件封装)
x

浙公网安备 33010602011771号