高阶组件
Higher-order Components(HOC)
高阶组件是以参数为组件,返回值为新组件的函数
本质上对组件进行一次拦截,然后进行处理,返回一个处理后的新组件
缺陷:
需要再原组件上进行包裹或者嵌套,如果大量使用HOC,将会产生非常多的嵌套,导致调试困难
会劫持props,在不遵守约定的情况下会产生冲突
场景 1:props
可以对函数组件和类组件进行处理.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import React, { PureComponent } from 'react'
function enhancedUser(OriginComponent) { class NewComponent extends PureComponent { constructor(props) { super(props)
this.state = { userInfo : { name: "codewhy", level:99 } } }
render() { return <OriginComponent {...this.props} {...this.state.userInfo}/> } } }
const Home = enhancedUser(function(props) { return (<h1>Home: {props.name} - {props.level}- {props.banners}</h1>) })
export class App extends PureComponent { render() { return ( <div> <Home banners={["xixihaha", "oooaa"]}/> <div>{this.props.name}</div> //类组件被包裹后,加强组件传入的数据同样也在其props中找 </div> ) } }
export default enhancedUser(App)
|
真实场景1: context共享
当存在多个组件需要使用context传递,此时传统方法需要再子组件中写
1 2 3 4 5 6 7
| <UserContext.Consumer> { value => { return <h2>Info User: {value.color}</h2> } } </UserContext.Consumer>
|
从而获取父组件传递的参数,太繁琐.
因此可以对这个包裹部分写成一个高阶组件.使用高阶组件包装子组件,export default withTheme(sonComps) 则可以实现简化.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import React, { PureComponent } from 'react' import ThemeContext from './context/theme_context' import Product from './Product'
export class App extends PureComponent { render() { return ( <div> <ThemeContext.Provider value={{color:"red", size:30}}> //context传递的 <Product title={"xixihaha"} /> //props传递的 </ThemeContext.Provider> </div> ) } }
export default App
import { createContext } from "react"; const ThemeContext = createContext() export default ThemeContext
import ThemeContext from "./theme_context" function withTheme(OriginComponent) { return (props) => { return( <ThemeContext.Consumer> { value => { return <OriginComponent {...value} {...props}/> //注意此处向组件中传递了context的value数据和父组件props的数据,因此子组件取数据时,直接在props统一取就行 } } </ThemeContext.Consumer> ) } }
export default withTheme
import React, { PureComponent } from 'react' import withTheme from './context/with_theme' export class Product extends PureComponent { render() { const { color, size, title } = this.props return ( <div>Product:{color}- {size}-{title}</div> ) } }
export default withTheme(Product)
|
真实场景2:检验授权
传统写法,写一个三元运算{ isLogin?:”需要登录”},为True展示页面,false不展示. 如果存在多个需要登录的地方,那么需要多次重复写这个三元运算.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| function loginAuth(OriginComponent) { return props => { const token = localStorage.getItem("token") if (token) { return <OriginComponent {...props}/> } else { return <h2> Please Login</h2> } } }
export default loginAuth
import React, { PureComponent } from 'react' import loginAuth from './login_auth'
export class cart extends PureComponent { render() { return ( <div>cart</div> ) } }
export default loginAuth(cart)
import React, { PureComponent } from 'react' export class App extends PureComponent { render() { return ( <div> <cart/> //此时不需要任何额外处理 </div> ) } }
export default App
|
真实场景3:生命周期
测试一个页面的渲染时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| function logRenderTime(OriginComponent) { return class extends PureComponent { UNSAFE_componentWillMount() { this.beginTime = new Date().getTime() }
componentDidMount() { this.endTime = new Date().getTime() const interval = this.endTime - this.beginTime console.log(`页面划分${interval}ms渲染`); }
render() { return <OriginComponent {...this.props}/> } } }
export default logRenderTime
import React, { PureComponent } from 'react' export class App extends PureComponent { render() { return ( <div> <h1>xxx</h1> </div> ) } }
export default logRenderTime(App)
|
Portals的使用
希望渲染的内容独立与父组件,甚至独立于当前挂载到的DOM元素(默认挂载到id为root的DOM元素上)
1 2 3 4 5 6 7 8 9 10 11 12
| export class App extends PureComponent { render() { return ( <div> <h1>App H1</h1> { createPortal(<h2>appp h2</h2>, document.querySelector("#why")) //(插入的内容, 挂载的地方) } </div> ) } }
|
Fragment的用法
可以用于包裹元素块,免得每次总要用div包起来.
和<>等价,不过注意,对于需要绑定key(如列表遍历的情况),只能用Fragment
1 2 3 4 5 6 7 8 9
| export default class App extends PureComponent { render() { return ( <Fragment> <h2>xixihaha</h2> </Fragment> ) } }
|
严格模式
包裹相关组件
1 2 3 4 5 6 7 8 9 10 11
| export default class App extends PureComponent { render() { return ( <StrictMode> <div> <h2>xixihaha</h2> </div> </StrictMode> ) } }
|
严格模式检测什么?
识别不安全的生命周期
使用过时的ref API
检查意外的副作用
React过渡动画react-transition-group
四个主要组件
- Transition:和平台无关的组件
- CSSTransition:通常使用CSSTransition完成过渡动画效果
- SwitchTransition:两个组件显示和隐藏切换时,使用该组件
- TransitionGroup:将多个动画组件包裹在其中,一般用于列表中元素的动画
CSSTransition
执行过程中,有三个状态:appear,enter,exit
它有三种状态,需要定义对应的CSS样式
- 第一类,开始状态,对应的类 -appear, -enter, exit
- 第二类,执行动画,对应的类 -appear-active, -enter-active, -exit-active
- 第三类,执行结束,对应的类是-appear-done, -enter-done, -exit-done
CSSTransition常见属性:
- className:动画class的名称
- timeout:过渡动画的时间
- appear是否在初次进行添加动画(需要和in同时设置为true)
- unmountOnExit:退出后卸载组件
- 部分构造函数,检测动画执行过程,
- onEnter:在进入动画之前出发
- onEntering:在应用进入动画时被触发
- onEntered:在应用进入动画结束后被触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| .why-enter { opacity:0 }
.why-enter-active { opacity: 1; transition: opacity 2s ease; }
.why-exit { opacity: 1; }
.why-exit-active { opacity: 0; transition: opacity 2s ease; }
-------------------------------------------- export class App extends PureComponent { constructor(props) { super(props) this.state = { isShow:true } }
render() { const { isShow} = this.state
return ( <div> <button onClick={e => this.setState({isShow:!isShow})}>Change</button> <CSSTransition in={isShow} unmountOnExit={true} classNames="why" timeout={2000} appear onEnter={e=> console.log("开始进入")}> //属性in表示在true和false中切换 <h2>haha</h2> </CSSTransition> </div> ) } }
|
SwitchTransition
主要属性mode:
in-out:新组件先进入,旧组件再移除
out-in:旧组件先移除,新组件再进入
把CSSTransition包起来就可以.主要此时在CSSTransition用的是key而不是 in
1 2 3 4 5 6 7 8 9 10
| return ( <div> <button onClick={e => this.setState({isLogin: !isLogin)}>{ isLogin? "退出":登录}</button> <SwitchTransition mode='out-in'> <CSSTransition key={isLogin?"exit":"login"} classNames="login" timeout={2000}> <h2>haha</h2> </CSSTransition> </SwitchTransition> </div> )
|
TransitionGroup
1 2 3 4 5 6 7
| <TransitionGroup component="ul"> <CSSTransition key={index} classNames="book" timeout={2000}> <li> <span>{item.name}-{item.price}</span> </li> </CSSTransition> </TransitionGroup>
|