React(二)

React(二)

​ 前端框架,快速开发页面,函数式编程,与后端api快速搭建

组件

react会将以小写字母开头的组件视为原生DOM标签,而组件名称必须以大写字母开口

组件的定义方式

以函数方式定义组件

1
2
3
function Welcome(props){
return <h1>hello,{props.name}</h1>
}

使用ES6的语法class定义组件

1
2
3
4
5
class Welcome extends React.component{
render(){
return <h1>hello,{props.name}</h1>;
}
}

引用组件

组件可以在输出中引用其他组件。在React中通常会以组件的形式表示。

组件被调用时可以携带参数,称为props,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Welcome(props){
return <h1>hello,{props.name}</h1>
}

function App(){
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahs" />
<Welcome name="hara" />
</div>
)
}

ReactDOM.render(
<App />
document.getElementById('root')
)

组件APi

在React中,组件以函数声明或者以Class方式声明。以Class方式声明时通常需要从React.Compoenent中继承。

React.Compoenent提供了生命周期api,因为生命周期的使用方式比较重要,这将在后文中介绍,这里首先介绍除了生命周期之外的其他API。

forceupdate:component.forceUpdate(callback)

默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。

调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate() 方法。如果标记发生变化,React 仍将只更新 DOM。

通常你应该避免使用 forceUpdate(),尽量在 render() 中使用 this.propsthis.state

错误处理api

static getDerivedStateFromError(error)

此生命周期会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state

componentDidCatch(error, info)

此生命周期在后代组件抛出错误后被调用。 它接收两个参数:

  1. error —— 抛出的错误。
  2. info —— 带有 componentStack key 的对象,

componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况:

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
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染可以显降级 UI
return { hasError: true };
}

componentDidCatch(error, info) {
// "组件堆栈" 例子:
// in ComponentThatThrows (created by App)
// in ErrorBoundary (created by App)
// in div (created by App)
// in App
logComponentStackToMyService(info.componentStack);
}

render() {
if (this.state.hasError) {
// 你可以渲染任何自定义的降级 UI
return <h1>Something went wrong.</h1>;
}

return this.props.children;
}
}

如果发生错误,你可以通过调用 setState 使用 componentDidCatch() 渲染降级 UI,但在未来的版本中将不推荐这样做。 可以使用静态 getDerivedStateFromError() 来处理降级渲染。

受控组件与非受控组件

受控和非受控

名词“受控”“非受控”通常用来指代表单的 inputs,但是也可以用来描述数据频繁更新的组件。用 props 传入数据的话,组件可以被认为是受控(因为组件被父级传入的 props 控制)。数据只保存在组件内部的 state 的话,是非受控组件(因为外部没办法直接控制 state)。

当一个派生 state 值也被 setState 方法更新时,这个值就不是一个单一来源的值了。

如果组件的状态只能由用户控制,那么就是非受控组件,如果组件的状态可以由用户和通过代码两种方式控制,那么就是受控组件

在React中没有类似于Vue中v-model的双向绑定功能。

1
2
3
4
5
6
7
8
9
class TestComponent extends React.Component {
constructor (props){
super(props);
this.state = {username: 'lindaidai' }
}
render () {
return <input name="username" value={this.state.username} />
}
}

受控组件的完整定义:

在Html的表单元素中,它们通常自己维护一套state,并随着用户的数据自己进行UI上的更新,这种行为不被我们程序所控制。而如果将React的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作,被react以这种方式控制取值的表单输入元素就是受控组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TestComponent extends React.Component {
constructor (props){
super(props);
this.state = {
username: 'lindaidai'
}
}
onChange (e){
this.setState({
username: e.target.value
})
}
render () {
return <input name="username" value={this.state.username}
onChange={(e)=> this.onChange(e)} />
}
}

受控组件注意事项

受控组件中有时会有派生state,即state的状态是根据props的值来进行变化

不建议将props直接复制到state中

最常见的误解就是 getDerivedStateFromPropscomponentWillReceiveProps 只会在 props “改变”时才会调用。实际上只要父级重新渲染时,这两个生命周期函数就会重新调用,不管 props 有没有“变化”。所以,在这两个方法内直接复制(unconditionally)props 到 state 是不安全的。这样做会导致 state 后没有正确渲染

错误使用1:在componentWillReceiveProps中直接使用prop初始化state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class EmailInput extends Component {
state = { email: this.props.email };

render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}

handleChange = event => {
this.setState({ email: event.target.value });
};

componentWillReceiveProps(nextProps) {
// 这会覆盖所有组件内的 state 更新!
// 不要这样做。
this.setState({ email: nextProps.email });
}
}

乍看之下还可以。 state 的初始值是 props 传来的,当在 <input> 里输入时,修改 state。但是如果父组件重新渲染,我们输入的所有东西都会丢失!(查看这个示例),即使在重置 state 前比较 nextProps.email !== this.state.email 仍然会导致更新。

这个小例子中,使用 shouldComponentUpdate ,比较 props 的 email 是不是修改再决定要不要重新渲染。但是在实践中,一个组件会接收多个 prop,任何一个 prop 的改变都会导致重新渲染和不正确的状态重置。加上行内函数和对象 prop,创建一个完全可靠的 shouldComponentUpdate 会变得越来越难。这个示例展示了这个情况。而且 shouldComponentUpdate 的最佳实践是用于性能提升,而不是改正不合适的派生 state。

错误使用1:在componentWillReceiveProps中比较前后两次props再初始化state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class EmailInput extends Component {
state = {
email: this.props.email
};

componentWillReceiveProps(nextProps) {
// 只要 props.email 改变,就改变 state
if (nextProps.email !== this.props.email) {
this.setState({
email: nextProps.email
});
}
}

// ...
}

现在组件只会在 prop 改变时才会改变。

但是仍然有个问题。想象一下,如果这是一个密码输入组件,拥有同样 email 的两个账户进行切换时,这个输入框不会重置(用来让用户重新登录)。因为父组件传来的 prop 值没有变化!这会让用户非常惊讶,因为这看起来像是帮助一个用户分享了另外一个用户的密码,

建议1:把组件包装成完全可控的组件

1
2
3
function EmailInput(props) {
return <input onChange={props.onChange} value={props.email} />;
}

建议2:为了在不同的页面切换不同的值,我们可以使用 key 这个特殊的 React 属性。当 key 变化时, React 会创建一个新的而不是更新一个既有的组件。 Keys 一般用来渲染动态列表,但是这里也可以使用。

每次 ID 更改,都会重新创建 EmailInput ,并将其状态重置为最新的 defaultEmail 值。(点击查看这个模式的演示) 使用此方法,不用为每次输入都添加 key,在整个表单上添加 key 更有位合理。每次 key 变化,表单里的所有组件都会用新的初始值重新创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class EmailInput extends Component {
state = { email: this.props.defaultEmail };

handleChange = event => {
this.setState({ email: event.target.value });
};

render() {
return <input onChange={this.handleChange} value={this.state.email} />;
}
}

<EmailInput
defaultEmail={this.props.user.email}
key={this.props.user.id}
/>

https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

封装组件为受控组件和非受控组件两种

组件间通信

父组件向子组件通讯: 父组件可以向子组件通过传 props 的方式,向子组件进行通讯

子组件向父组件通讯: props+回调的方式,父组件向子组件传递props进行通讯,此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
42
43
44
45
46
47
48
49
50
51
import React from "react";

function Child1(props) {
return (
<div className="child">
<p>{`兄弟1接收到的文本:${props.fatherText}`}</p>
</div>
);
}

class Child2 extends React.Component {
state = { text: "兄弟2文本" };

//调用了父组件传入的 changeFatherText 方法
changeText = () => {
this.props.changeFatherText(this.state.text);
};

render() {
return (
<div className="child">
<button onClick={this.changeText}>点击更新兄弟1文本为兄弟2文本</button>
</div>
);
}
}

export default class Father extends React.Component {
// 初始化父组件的 state
state = {
text: "父组件的文本"
};

// 传给 Child2 组件按钮的监听函数,用于更新父组件 text 值(这个 text 值同时也是 Child1 的 props)
changeText = (newText) => {
this.setState({ text: newText });
};

// 渲染父组件
render() {
return (
<div className="father">
{/* 引入 Child1 组件,并通过 props 中下发具体的状态值 实现父-子通信 */}
<Child1 fatherText={this.state.text} />

{/* 引入 Child2 组件,并通过 props 中下发可传参的函数 实现子-父通信 */}
<Child2 changeFatherText={this.changeText} />
</div>
);
}
}

跨层级通信: Context设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再适合不过

全局状态管理工具: 借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态

context api

组件间层层嵌套时,传props的过程中会产生大量的…props或者propName={this.props.propValue},导致代码异常丑陋,比如exzzzzz

1
2
3
4
5
6
7
8
9
<App>
<Switcher toggleState = {this.state.toggle}>
<Pannel toggleState = {props.toggleState}>
<div onClick={handleClick}>
{props.toggleState?'1':'0'}
</div>
</Pannel>
</Switcher>
</App>

引入context api代码

简易版,通过provide的value传值,通过consumer的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
import React,{createContext} from 'react'

const {Provider,Consumer} = createContext('color');

class DeliverComponent extends React.component{
state = {
color:'orange',
handleClick:() =>{
this.setState({ color:'red'})
}
}
render(){
return (
<Provider value= {this.state}>
<MidComponent/>
</Provider>
)
}
}

const MidComponent = () => <Receiver />

const Receiver = () =>(
<Consumer>
{({color,handleClick}) =>
<div style ={{color}} onClick={()=>{handleClick()}}>
hello,world
</div>}
</Consumer>
)

const App =()=> <DeliverComponent/>

export default App;

复杂版

引入context api,创建provider和consumer

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
//togglecontext.js
import React,{createContext} from 'react'
//创建上下文
const ToggleContext = createContext({
toggle:true,
handleToggle:()=>{}
})

//创建provider
export class ToggleProvider extends React.component{
state = {
toggle:true,
handleToggle:this.handleToggle
}

render() {
return (
<ToggleContext.Provider value={this.state}>
{this.props.children}
</ToggleContext.Provider>
)
}
}
//创建consumer
export const ToggleConsumer = ToggleContext.Consumer

通过provider包裹组件传递value值可以使组件共享provider中的state,通过consumer获取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
import React from 'react';
import {ToggleProvider,ToggleConsumer} from './ToggleContext'

function App(){
return (
<ToggleProvider>
<Switcher></Switcher>
</ToggleProvider>
)
}

const Switcher = () =>{
return <Pannel/>
}

const Pannel = () =>{
return (
<ToggleConsumer>
{({toggle.handleToggle})=>
<div onClick={()=>handleToggle()}>
{toggle?'1':'0'}
</div>
}
</ToggleConsumer>
)
}

export default App

ref、OnRef与forwardRef

在典型的 React 数据流中,props 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。但是,在某些情况下,你需要在典型数据流之外强制修改子组件/元素

适合使用 refs 的情况:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。
  • 集成第三方 DOM 库。

ref 的值根据节点的类型而有所不同:

  • 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
  • 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
  • 默认情况下,你不能在函数组件上使用 ref 属性(可以在函数组件内部使用),因为它们没有实例:
    • 如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用)
    • 或者可以将该组件转化为 class 组件。

父组件通过ref可以拿到子组件的方法和属性

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import React, { Component, Fragment } from "react";
import UncontrolledEmailInput from "./UncontrolledEmailInput";

export default class AccountsList extends Component {
inputRef = React.createRef();

state = {
selectedIndex: 0
};

handleChange = index => {
this.setState({ selectedIndex: index }, () => {
const selectedAccount = this.props.accounts[index];
this.inputRef.current.resetEmailForNewUser(selectedAccount.email);
});
};
render() {
const { accounts } = this.props;
const { selectedIndex } = this.state;
const selectedAccount = accounts[selectedIndex];
return (
<Fragment>
<h1>
This demo illustrates resetting an uncontrolled component with an
instance method
</h1>
<blockquote>First, make an edit to the account "One" email.</blockquote>
<UncontrolledEmailInput
defaultEmail={selectedAccount.email}
ref={this.inputRef}
/>
<blockquote>Next, select account "Two" below.</blockquote>
<p>
Accounts:
{this.props.accounts.map((account, index) => (
<label key={account.id}>
<input
type="radio"
name="account"
checked={selectedIndex === index}
onChange={() => this.handleChange(index)}
/>{" "}
{account.name}
</label>
))}
</p>
<p>
Even though both accounts have the same "committed" email, toggling
between the two properly resets the "draft" email state. Read the
inline comments in <code>UncontrolledEmailInput.js</code> to learn
why.
</p>
</Fragment>
);
}
}
/// 子组件
import React, { Component } from "react";

// This is an example of an "uncontrolled" component.
// We call it this because the component manages its own "draft" state.
export default class UncontrolledEmailInput extends Component {
// Default the "draft" email to the value passed in via props.
state = {
email: this.props.defaultEmail
};

// Imperative method to reset "draft" email state.
// Call this method using a component ref.
resetEmailForNewUser(defaultEmail) {
this.setState({ email: defaultEmail });
}

handleChange = event => {
this.setState({ email: event.target.value });
};

render() {
return (
<label>
Email: <input onChange={this.handleChange} value={this.state.email} />
</label>
);
}
}

Onref通过props将子组件的组件实例当作参数,通过回调传到父组件,然后在父组件就可以拿到子组件的实例了,拿到实例就可以调用它的方法了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Son from './son'

class Father extends React.Component {
sonRef = (ref) => {
this.child = ref
}

render() {
return (
<div>
<Son onRef={this.sonRef}/>
</div>
)
}
}

ref可以直接获得dom信息,非受控组件可以采用这种方式获取值而不进行其他操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React,{ Component } from 'react'

export class UnControl extends Component {
constructor (props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (e) => {
e.preventDefault();
console.log('input内的值为',this.inputRef.current.value);
}
render () {
return (
<form onSubmit={e => this.handleSubmit(e)}>
<input defaultValue="lindaidai" ref={this.inputRef}/>
<input type="submit" value="提交"/>
</form>
)
}
}

forwardRef多用于Ref 转发。Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧。对于大多数应用中的组件来说,这通常不是必需的。通常不建议这样做,因为它会打破组件的封装,但它偶尔可用于触发焦点或测量子 DOM 节点的大小或位置。但其对某些组件,尤其是可重用的组件库是很有用的。

没有使用forwardRef时,父组件传入子组件ref属性,此时ref指向的是子组件本身。但是如果想让child指向的是Childbutton呢?此时在子组件中新建一个buttonRef,并作为拓展的props由父组件控制,新增一个字段如buttonRef。所以 React 提供了 forwardRef,用于将 ref 转发。这样子组件在提供内部的 dom 时,不用扩充额外的 ref 字段

Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

1
2
3
4
5
6
7
8
9
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

转发ref在父组件作为别的组件的子组件时会比较方便, 也就是HOC

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
import Button from './Button';
const LoggedButton = logProps(Button);

const ref = React.createRef();

// LoggedButton 组件是高阶组件(HOC)LogProps。
// 尽管渲染结果将是一样的,
// 但我们的 ref 将指向 LogProps 而不是内部的 Button 组件!
// 这意味着我们不能调用例如 ref.current.xxx() 这样的方法
<LoggedButton label="Click Me" handleClick={handleClick} ref={ref} />;

function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}

render() {
return <WrappedComponent {...this.props} />;
}
}

return LogProps;
}

使用forwardRef和useImperativeHandle限制父组件调用子组件的Api

Button组件提供了onChange回调,外部组件可以传入onChange方法获取实时的status,Button内部则通过onToggleStatus控制状态

如果现在另一个开发人员开发外部组件时,想要实现在外部实现第二个按钮实时控制和同步显示Button的状态。此时他已经可以通过onChange实时同步状态,而从外部修改Button状态则一般有两种方式:

  1. 修改Button组件为纯函数组件,将其状态和修改状态的方法提升至父组件或者状态管理工具中。
  2. 通过ref拿到该组件,通过ref.current.onToggleStatus()的方式修改子组件状态。

ref的其他用法

16.3之前可以通过字符或者回调函数两个方式获取ref

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// string ref
class MyComponent extends React.Component {
componentDidMount() {
this.refs.myRef.focus();
}

render() {
return <input ref="myRef" />;
}
}

// callback ref
class MyComponent extends React.Component {
componentDidMount() {
this.myRef.focus();
}

render() {
return <input ref={(ele) => {
this.myRef = ele;
}} />;
}
}

string ref 就已被诟病已久,React 官方文档中如此声明:"如果你目前还在使用 this.refs.textInput 这种方式访问 refs ,我们建议用回调函数或 createRef API 的方式代替。"

吐槽内容主要有以下几点:

  1. string ref 不可组合。 例如一个第三方库的父组件已经给子组件传递了 ref,那么我们就无法在在子组件上添加 ref 了。 另一方面,回调引用没有一个所有者,因此您可以随时编写它们。

  2. string ref 的所有者由当前执行的组件确定。 这意味着使用通用的“渲染回调”模式(例如react),错误的组件将拥有引用(它将最终在react上而不是您的组件定义renderRow)

  3. string ref 不适用于Flow之类的静态分析。 Flow不能猜测框架可以使字符串ref“出现”在react上的神奇效果,以及它的类型(可能有所不同)。 回调引用比静态分析更友好。

  4. string ref 强制React跟踪当前正在执行的组件。 这是有问题的,因为它使react模块处于有状态,并在捆绑中复制react模块时导致奇怪的错误。在 reconciliation 阶段,React Element 创建和更新的过程中,ref 会被封装为一个闭包函数,等待 commit 阶段被执行,这会对 React 的性能产生一些影响。

而callback ref则一直可以。React 将在组件挂载时,会调用 ref 回调函数并传入 DOM 元素,当卸载时调用它并传入 null。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

16.3之后class组件中有了createRef,相比于之前的ref使用方式,优点:

  • 相对于 callback ref 而言 React.createRef 显得更加直观,避免了 callback ref 的一些理解问题。

React.createRef 的缺点:

  1. 性能略低于 callback ref
  2. 能力上仍逊色于 callback ref,例如上一节提到的组合问题,createRef 也是无能为力的。

列表组件

使用key时,不能使用数组的index作为列表组件的key

使用index作为key的列表,向列表中添加或删除某些项时可能导致错误的显示。因为key是连接真实DOM的标识,当更改后的key与更改前的key相同时,react会认为前后的组件是相同的,但其实这两项并不一样

Constructor

class组件中有constructor构造函数,有两个目的

1.初始化this.state

2.函数方法绑定到实例

1
2
3
4
5
constructor(props) {
super(props);
this.state = { counter: 0 };
this.handleClick = this.handleClick.bind(this)
}

使用箭头函数则不需要将事件在constructor中绑定

props默认值

对于函数组件,设置函数的defaultprops属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react'

function About (props) {
const { name, age } = props
return (
<div>
<p>{ name }</p>
<p>{ age }</p>
</div>
)
}

About.defaultProps = {
name: 'ReoNa',
age: 22
}

export default About

对于类组件,我们直接定义 static defaultProps设置默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React, { Component } from 'react'

class Header extends Component {

static defaultProps = {
name: 'Aimyon',
age: 25
}

render () {
const { name, age } = this.props
return (
<div>
<p>{ name }</p>
<p>{ age }</p>
</div>
)
}
}

export default Header

修改props的方法

父组件使用ref

父组件对子组件传入改变props的方法,由自组件调用

HOC与render Props

HOC

HOC的基本原理可以写成这样:

1
2
3
4
5
6
7
const HOCFactory = (Component) => {
return class HOC extends React.Component {
render(){
return <Component {...this.props} />
}
}
}

HOC最大的特点就是:接受一个组件作为参数,返回一个新的组件。

HOC的优点:

  • 支持ES6,光这一项就战胜了mixins
  • 复用性强,HOC是纯函数且返回值仍为组件,在使用时可以多层嵌套,在不同情境下使用特定的HOC组合也方便调试。
  • 同样由于HOC是纯函数,支持传入多个参数,增强了其适用范围。

HOC的缺点:

  • 当有多个HOC一同使用时,无法直接判断子组件的props是哪个HOC负责传递的。
  • 重复命名的问题:若父子组件有同样名称的props,或使用的多个HOC中存在相同名称的props,则存在覆盖问题,而且react并不会报错。当然可以通过规范命名空间的方式避免。
  • 在react开发者工具中观察HOC返回的结构,可以发现HOC产生了许多无用的组件,加深了组件层级。
  • 同时,HOC使用了静态构建,即当AppWithMouse被创建时,调用了一次withMouse中的静态构建。而在render中调用构建方法才是react所倡导的动态构建。与此同时,在render中构建可以更好的利用react的生命周期。

render prop 的出现解决了以上问题

render-props

相比于直接将 <Cat> 写死在 <Mouse> 组件中,并且有效地更改渲染的结果,我们可以为 <Mouse> 提供一个函数 prop 来动态的确定要渲染什么 —— 一个 render prop

Render Props 的核心思想是,通过一个函数将class组件的state作为props传递给纯函数组件

具有 render prop 的组件接受一个返回 React 元素的函数,并在组件内部通过调用此函数来实现自己的渲染逻辑

render prop 是因为模式才被称为 render prop ,你不一定要用名为 render 的 prop 来使用这种模式。事实上, 任何被用于告知组件需要渲染什么内容的函数 prop 在技术上都可以被称为 “render prop”.

children prop 并不真正需要添加到 JSX 元素的 “attributes” 列表中。相反,你可以直接放置到元素的内部

由于这一技术的特殊性,当你在设计一个类似的 API 时,你或许会要直接地在你的 propTypes 里声明 children 的类型应为一个函数

1
2
3
4
5
6
7
8
9
<Mouse children={mouse => (
<p>鼠标的位置是 {mouse.x},{mouse.y}</p>
)}/>

<Mouse>
{mouse => (
<p>鼠标的位置是 {mouse.x},{mouse.y}</p>
)}
</Mouse>

注意:

将 Render Props 与 React.PureComponent 一起使用时要小心

如果你在 render 方法里创建函数,那么使用 render prop 会抵消使用 React.PureComponent 带来的优势。因为浅比较 props 的时候总会得到 false,并且在这种情况下每一个 render 对于 render prop 将会生成一个新的值。

render prop的优点:

  • 支持ES6,和HOC一样
  • 不用担心prop的命名问题,在render函数中只取需要的state
  • 相较于HOC,不会产生无用的空组件加深层级
  • 最重要的是,这里的构建模型是动态的,所有改变都在render中触发,能更好的利用react的生命周期。

评论

You forgot to set the app_id or app_key for Valine. Please set it in _config.yml.

 

本文章阅读量:

  0

IT学徒、技术民工、斜杠青年

机器人爱好者、摄影爱好者

PS、PR、LR、达芬奇潜在学习者

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×