React学习(二)
文章目录
- 1. React性能优化SCU
- 2. 获取DOM方式refs
- 2.1 获取原生DOM的三种方式
- 2.2 获取组件实例
- 3. 受控和非受控组件
- 3.1 受控组件(推荐🌟)
- 3.1.1 input
- 3.1.2 checkbox (单选/多选)
- 3.1.3 select
- 3.2 非受控组件
- 4. 高阶组件 hoc
- 4.1 高阶组件写法定义
- 4.2 应用场景
- 5. 简要知识🧀
- 5.1 Portals
- 5.2 Fragment
- 5.3 StrictMode(了解)
- 6. 过渡动画
- 6.1 react-transition-group
- 6.2 CSSTransition
- 6.3 SwitchTransition
- 6.4 TransitionGroup
1. React性能优化SCU
先来了解一下React
渲染流程和更新流程;
- React的渲染流程:
- React的更新流程:
- keys的优化:
render
的调用
我们知道,当state
和props
数据更新之后都会触发render()
,但是其实有时候子组件没有使用到这些数据也会被迫更新,这就很浪费性能了。
所以React
给我们提供一个生命周期函数shouldComponentUpdate
,返回false的时候该组件就不会被更新,详细情况如下:
但是每次通过判断新旧数据是否一致来决定render()是否执行,这是在是太麻烦了,所以React
又给我们提供了PureComponent
来解决这种困扰。
怎么使用呢?
之前我们创建类组件总是继承自React.component
,现在我们可以换成继承自PureComponent
,
import { Component,PureComponent } from 'react'
export class SetStateTest extends Component {
}// 改为
export class SetStateTest extends PureComponent {
}
在函数式组件中怎么解决这个问题呢? ----使用memo
!!!
import ThemeContext from "./context/theme-context";
// 函数式组件const AppFunc = memo(function AppFunc() {// 返回值与类组件返回值一致return (...)
})export default AppFunc
这就是对React的性能优化(部分内容)了。
- 使用
PureComponent
需要注意的地方:
2. 获取DOM方式refs
2.1 获取原生DOM的三种方式
一般我们不去获取原生DOM,但是这三种方式我们需要掌握。
- 第一种:传入一个
createRef()
定义的对象; - 第二种:传入一个回调函数;
- 第三种:
undefine
或null
import { PureComponent, createRef } from 'react';export class HelloWorld extends PureComponent {render() {return (<div>HelloWorld</div>)}
}export class RefBind extends PureComponent {constructor(props) {super(props)this.titleRef = createRef()this.titleEl = null;}getDOMRef() {console.log('ref对象2', this.titleRef.current);console.log('ref对象3', this.titleEl);}render() {return (<div><h1>使用ref获取DOM</h1><h2 ref={this.titleRef}>ref对象1</h2><h2 ref={el => this.titleEl = el}>ref对象2</h2><button onClick={() => this.getDOMRef()}>获取ref对象</button></div>)}
}export default RefBind
注:React18+
之后的字符串形式的ref已被弃用。
2.2 获取组件实例
学习了获取原生DOM的方法,那么同样的,我们也可以使用ref
获取到组件实例。
- 获取类组件实例
获取类组件实例与获取原生DOM方法一样,这里的ref
可以获取到整个组件实例。
import { PureComponent, createRef, forwardRef } from 'react';export class HelloWorld extends PureComponent {test() {console.log('hw组件实例', this);}render() {return (<div>HelloWorld</div>)}
}export class RefBind extends PureComponent {constructor(props) {super(props)this.hwRef = createRef()}getDOMRef() {console.log('hw组件实例', this.hwRef.current.test());}render() {return (<div><h1>使用ref获取DOM</h1><HelloWorld ref={this.hwRef} /><button onClick={e => this.getDOMRef()}>获取HelloWorld组件实例</button></div>)}
}export default RefBind
- 获取函数式组件实例
因为函数式组件没有实例,这里ref
使用React.forwardRef()
进行绑定,且只能获取到组件中的某个DOM
import { PureComponent, createRef, forwardRef } from 'react';const HelloWorldFunc = forwardRef(function (props, ref) {return (<div ref={ref}>HelloWorld</div>)
})export class RefBind extends PureComponent {constructor(props) {super(props)this.hwfRef = createRef()}getDOMRef() {console.log('hwf组件实例', this.hwfRef.current);}render() {return (<div><HelloWorldFunc ref={this.hwfRef} /><button onClick={e => this.getDOMRef()}>获取HelloWorld组件实例</button></div>)}
}export default RefBind
3. 受控和非受控组件
什么是受控组价?什么是非受控组件呢?
import { Component } from 'react'export class Control extends Component {constructor(props) {super(props)this.state = {name: 'cr'}}changeInput(e) {console.log(e.target.value)this.setState({name: e.target.value})}render() {const { name } = this.statereturn (<div><h1>受控组价和非受控组件</h1><h2>受控组件 name:{name}</h2><input type="text" value={name} onChange={e => this.changeInput(e)} /><h2>非受控组件</h2><input type="text" /></div>)}
}export default Control
这个案例中第一个input
的value受我们的控制,第二个input
自己维护自己的状态,不受我们控制,简单来说就是受控组件和非受控组件了。
3.1 受控组件(推荐🌟)
3.1.1 input
value
值由我们自己控制时input
就变成了受控组件。
import { Component } from 'react'export class Control extends Component {constructor(props) {super(props)this.state = {username: '',password: ''}}changeInput(e) {this.setState({[e.target.name]: e.target.value})}render() {const { username, password } = this.statereturn (<div><form action="#" onAbort={e => e.preventDefault()}><label htmlFor="username">用户名:<input type="text" name="username" id="username" value={username} onChange={e => this.changeInput(e)} /></label><br /><label htmlFor="password">密码:<input type="password" name="password" id="password" value={password} onChange={e => this.changeInput(e)} /></label></form ></div>)}
}
3.1.2 checkbox (单选/多选)
checkbox
我们一般使用它的isChecked
属性判断是否被选中;
import { Component } from 'react'export class Control extends Component {constructor(props) {super(props)this.state = {isAgree: false,hobbies: [{ id: "nail", value: '美甲💅', isChecked: false },{ id: "ktv", value: '唱K🎤', isChecked: false },{ id: "sleep", value: '睡觉💤', isChecked: false }]}}handleSubmit(e) {// 阻止默认事件发生e.preventDefault()console.log('同意协议', this.state.isAgree)console.log('爱好', this.state.hobbies.filter(hobby => hobby.isChecked).map(hobby => hobby.value))}// 单选changeAgree(e) {this.setState({isAgree: e.target.checked})}// 多选changeHobby(e, index) {const hobbies = [...this.state.hobbies]hobbies[index].isChecked = !hobbies[index].isCheckedthis.setState({hobbies})}render() {const { username, password, isAgree, hobbies } = this.statereturn (<div><form action="#" onSubmit={e => this.handleSubmit(e)}><label htmlFor="agree"><input type="checkbox" name="agree" id="agree" checked={isAgree} onChange={e => this.changeAgree(e)} />同意协议</label><div>爱好:{hobbies.map((hobby, index) => (<label htmlFor="nail" key={hobby.id}><input type="checkbox" name="hobby" id={hobby.id} onChange={e => this.changeHobby(e, index)} />{hobby.value}</label>))}</div><button>提交</button></form ></div>)}
}export default Control
3.1.3 select
select
我们一般使用e.target.selectedOptions
取获取多选选中的元素;
e.target.selectedOptions
是伪数组;- 可以通过
Array.from
转化为数组再进行数据处理;
React
推荐大多数情况下使用受控组件来管理表单数据:
- 一个受控组件中,数据是由
React
组件来管理的; - 另一种是使用非受控组件,数据由原生DOM来管理;
如果要使用非受控组件
,可以使用ref
绑定DOM进行数据的获取和处理。
3.2 非受控组件
4. 高阶组件 hoc
学习过js高级
的可能都了解高阶函数
,
同样的,高阶组件和高阶函数有异曲同工之处:
4.1 高阶组件写法定义
import { PureComponent } from "react"function enhancedComponent(OriginComponent) {class NewComponent extends PureComponent {constructor(props) {super(props)this.state = {userInfo: {name: "陈der",age: 18}}}render() {// this.props --- 传递原本组件的props// this.state.userInfo --- 高阶组件中定义的用户信息return (<OriginComponent {...this.props} {...this.state.userInfo} />)}}return NewComponent
}export default enhancedComponent
4.2 应用场景
- 高阶组件➕
context
我们知道使用context
时会用到context.Provider
或者context.Consumer
,若是每次都写,那么将会产生大量重复的代码,所以我们可以使用高阶组件进行封装。下面是ThemeContext
➕高阶组件的一个应用示例:
import ThemeContext from "../../context/theme-context"function withTheme(OriginComponent) {return (props) => {return (<ThemeContext.Consumer>{theme => <OriginComponent {...props} {...theme} />}</ThemeContext.Consumer>)}
}export default withTheme
登录鉴权
一般情况下,有些页面需要在登录状态下才能查看,这就需要在渲染前加判断是否展示相关页面,这时候我们就可以使用高阶组件,若登录则返回相关页面,若未登录就返回首页或其他状态提示页面。
function loginAuth(OriginComponent) {return props => {const token = localStorage.getItem('token')if (token) {return <OriginComponent {...props} />} else {return (<div><p>请先登录</p></div >)}}
}export default loginAuth
- 监听生命周期的执行
总结下来,高阶组件的作用就是拦截即将要渲染的组件,对数据状态等做加以判断再进行条件渲染。
回看之前学习的ref的转发
,我们使用forwardRef
接收转发传入的ref
;
还有memo
包裹函数式组件,替代类组件的PureComponent
;
这些都是高阶组件的用法
之后学习了Hooks可能HOC就不太需要了,但是初学还是需要了解一下。
5. 简要知识🧀
5.1 Portals
index.html
中添加<div id="modal"></div>
import { PureComponent } from 'react';
import { createPortal } from 'react-dom';export class Modal extends PureComponent {render() {return (<div>{createPortal(this.props.children, document.querySelector("#modal"))}</div>)}
}export default Modal
5.2 Fragment
Fragment
可作为render
的根元素,在浏览器中不会像下图右侧情况出现div
根元素。
无key
时可省略Fragment
,有key
时必须写完整用法。
// 完整写法<Fragment key={'Cart'}><h1>Cart</h1></Fragment>
// 简写<><h1>Cart</h1></>
5.3 StrictMode(了解)
我们知道在严格模式下js
的代码检查会严格一些,React
也为我们提供了StrictMode
组件来代替use strict
,主要能检测到的错误有以下几种情况:
了解即可,在代码书写规范的情况下也不一定需要。
6. 过渡动画
6.1 react-transition-group
<button onClick={() => this.setState({ isShow: !isShow })}>切换</button><CSSTransitionnodeRef={nodeRef}in={isShow}classNames="fade"timeout={1000}unmountOnExit={true}appear={true}onEnter={el => console.log('进入')}onEntering={el => console.log('进入中')}onEntered={el => console.log('进入完成')}><h1 ref={nodeRef}>sugdfvsdbhvjs</h1></CSSTransition>
.fade-appear, .fade-enter{opacity: 0;
}.fade-appear, .fade-enter-active{opacity: 1;transition: opacity 1000ms;
}.fade-exit{opacity: 1;
}
.fade-exit-active{opacity: 0;transition: opacity 1000ms;
}
6.2 CSSTransition
CSSTransition
常见属性:
in
:触发进入或者退出状态- 如果添加了
unmountOnExit=true
,那么该组件会在执行退出动画结束后被移除掉: - 当in为true时,触发进入状态,会添加-enter、.enter-.acitve的class开始执行动画,当动画执行结束后,会移除两个class,并旦添加-enter-done的class;
- 当in为falsel时,触发退出状态,会添加-exit.-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;
- 如果添加了
classNames:
动画class的名称- 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done;
timeout
:过渡动画的时间appear
:- 是否在初次进入添加动画(需要和in同时为true)
unmountOnExit
:退出后卸载组件- 其他属性可以参考官网来学习:
https://reactcommunity.org/react-transition-group/css-transition/
CSSTransition
对应的钩子函数
:主要为了检测动画的执行过程,来完成一些JavaScript的操作
公onEnter
:在进入动画之前被触发,onEntering
:在应用进入动画时被触发;onEntered
:在应用进入动画结束后被触发;
6.3 SwitchTransition
<SwitchTransition mode='out-in'><CSSTransitionkey={isShow ? 'login' : 'exit'} // 通过key来区分不同的元素nodeRef={nodeRef}in={isShow}classNames="login"timeout={1000}unmountOnExit={true}><button ref={nodeRef} onClick={() => this.setState({ isShow: !isShow })}>{isShow ? 'login' : 'exit'}</button></CSSTransition></SwitchTransition>
.login-enter{transform: translateX(-100px);opacity: 0;
}.login-enter-active{transition: all 1000ms ease;opacity: 1;transform: translateX(0);
}.login-exit{transform: translateX(0);opacity: 1;
}
.login-exit-active{transition: all 1000ms ease;opacity: 0;transform: translateX(100px);
}