【React】非受控组件

DOM这块是非之地,React并不想涉足其中,所以更多时候会我们会使用受控组件。
但是,如果想蹚浑水,非操作DOM 不可,React也提供了ref这一解决方法。
不过,ref只能在 class组件上使用,不能用于function组件。
所谓受控组件,是指 React组件的state同步记录表单数据,表单数据由React控制;
所谓非受控组件,就是指 表单数据不受React控制,而是由DOM节点自己控制。
而React里获取DOM的方式正是通过ref

使用ref

ref是特殊的props,使用ref有两种方式。

ref是一个对象
//react.development.js
function createRef() {
  var refObject = {
    current: null
  };
  {
    Object.seal(refObject);
  }
  return refObject;
}

React.createRef()返回一个带current属性的对象,且current的初始值是null。
ref作用在HTML元素上,挂载完成后,current属性就是该元素所在DOM节点;
ref作用在React组件上,挂载完成后,current属性就是该组件实例。

import React from "react";
import ReactDOM from "react-dom";

class NameForm extends React.Component{
    constructor(props){
        super(props);
        this.inputRef = React.createRef();
        console.log(Object.assign({},this.inputRef));
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    handleSubmit(e){
        console.log(this.inputRef.current.value);
        e.preventDefault();
    }
    componentDidMount(){
        console.log(Object.assign({},this.inputRef));
    }
    render(){
        return (
            <form onSubmit={this.handleSubmit}>
                <label>名字:<input type='text' name='name' ref={this.inputRef} defaultValue='Nicholas'/></label>
                <input type='submit' value='提交'/>
            </form>
        )
    }
}

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

在这里插入图片描述

ref是一个函数

ref是一个函数时,组件装载过程中会调用这个函数(在componentDidMount之前调用),且函数参数是ref所在DOM节点或组件实例。
在这里插入图片描述

import React from "react";
import ReactDOM from "react-dom";

class NameForm extends React.Component{
    constructor(props){
        super(props);
        this.inputRef = this.inputRef.bind(this);
        this.handleSubmit = this.handleSubmit.bind(this);
    }
    handleSubmit(e){
        console.log(this.input.value);
        e.preventDefault();
    }
    inputRef(input){
        this.input = input;
    }
    render(){
        return (
            <form onSubmit={this.handleSubmit}>
                <label>名字:<input type='text' name='name' ref={this.inputRef} defaultValue='Nicholas'/></label>
                <input type='submit' value='提交'/>
            </form>
        )
    }
}
ReactDOM.render(
    <NameForm/>,
    document.getElementById('root')
)

React的思想是UI=render(data),数据驱动视图,开发人员只需要关心要渲染成什么样子,不必关心怎么去操作DOM、如何实现渲染。
非受控组件则让我们重新回到了操作DOM的时代。
如果操作DOM是非必要的,尽量使用受控组件,让React全权在握,毕竟它具有这种能力。

转发ref

有两种方式。

第一种,通过props传递
import React from "react";
import ReactDOM from "react-dom";

class CustomTextInput extends React.Component{
    render(){
        return <input type='text' ref={this.props.inputRef} />
        // return <input type='text' ref={this.props.ref} />
    }
}

class Form extends React.Component{
    constructor(props){
        super(props);
        this.inputElm = React.createRef();
    }
    componentDidMount(){
        this.inputElm.current.focus();
    }
    render(){
        return <CustomTextInput inputRef={this.inputElm} />
       // return <CustomTextInput ref={this.inputElm} />
    }
}

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

将ref作为props从父组件传递给子组件,但如果像下面这样,

class Form extends React.Component{
    render(){
        return <CustomTextInput ref={this.inputElm} />
    }
}
class CustomTextInput extends React.Component{
    render(){
        return <input type='text' ref={this.props.ref} />
    }
}

React会给出警告:
‘ref’ is not a prop. Trying to access it will result in ‘undefined’ being returned. If you need to access the same value within the child component, you should pass it as a different prop.

因为ref是特殊的props,子组件无法获取,但可以使用其他名称,比如inputRef来传递 。

第二种,React.forwardRef(render)

React.forwardRef(render)
render,接收两个参数,返回一个JSX元素。
第一个参数:从父组件传递过来的props,该props不包含ref
第二个参数:从父组件传递过来的ref

  • 使用render的第一个参数:props
import React from "react";
import ReactDOM from "react-dom";

const AutoFocusTextInput = React.forwardRef((props,ref) => {
    //console.log(props);
    //console.log(ref);
    return <input type='text' ref={props.inputRef} />
})
class Form extends React.Component{
    constructor(props){
        super(props);
        this.inputElm = React.createRef();
    }
    componentDidMount(){
        this.inputElm.current.focus();
    }
    render(){
        return <AutoFocusTextInput inputRef={this.inputElm} />
    }
}
ReactDOM.render(
    <Form />,
    document.getElementById('root')
)

在这里插入图片描述

  • 使用render的第二个参数:ref
import React from "react";
import ReactDOM from "react-dom";

const AutoFocusTextInput = React.forwardRef((props,ref) => {
    //console.log(props);
    //console.log(ref);
    return <input type='text' ref={ref} />
})
class Form extends React.Component{
    constructor(props){
        super(props);
        this.inputElm = React.createRef();
    }
    componentDidMount(){
        this.inputElm.current.focus();
    }
    render(){
        return <AutoFocusTextInput ref={this.inputElm} />
    }
}
ReactDOM.render(
    <Form />,
    document.getElementById('root')
)
  • React.forwardRef(render)在高阶组件中的应用
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from "./app.js";

ReactDOM.render(<App/>,document.querySelector('#root'));
//app.js
import React from 'react';
import FancyButton from "./fancyButton.js";

class App extends React.Component{
    constructor(props){
        super(props);
        this.elm = React.createRef();
        this.handleClick = () => {
            console.log(this.elm.current);
        }
    }
    render(){
        return <FancyButton 
            label="click me"
            ref={this.elm}
            handleClick={this.handleClick}
        />
    }
}

export default App;
//fancyButton.js
import React from "react";
import "./fancyButton.css";
const FancyButton = React.forwardRef((props,ref) => {
    const {label,handleClick} = props;
    return <button className='fancyButton' ref={ref} onClick={handleClick}>{label}</button>
})
export default FancyButton;
//fancyButton.css
.fancyButton{
    outline:0;
    background-color:lightsteelblue
}

点击click me,控制台打印出<button class="fancyButton">click me</button>

高阶组件其实就是一个函数,它接受一个组件作为参数,返回一个新组件。
修改fancyButton.js,其中,HOC(FancyButton),组合了 原组件FancyButton,返回了新组件NewComponent

//fancyButton.js
import React from "react";
import "./fancyButton.css";

function HOC(WrappedComponent){
    class NewComponent extends React.Component{
        componentDidUpdate(props){
            console.log(props,this.props);
        }
        render(){
            return <WrappedComponent {...this.props} />
        }
    }
    return NewComponent;
}

const FancyButton = React.forwardRef((props,ref) => {
    const {label,handleClick} = props;
    return <button className='fancyButton' ref={ref} onClick={handleClick}>{label}</button>
})

export default HOC(FancyButton);

点击click me,控制台打印的不是<button class="fancyButton">click me</button>,而是NewComponent
在这里插入图片描述
怎么办?React.forwardRef(render) 可以帮上忙。

//fancyButton.js
import React from "react";
import "./fancyButton.css";

function HOC(WrappedComponent){
    class NewComponent extends React.Component{
        componentDidUpdate(props){
            console.log(props,this.props);
        }
        render(){
        //分离出theRef,除theRef外的其它props直接透传
            const {theRef,...restProps} = this.props;
            return <WrappedComponent ref={theRef} {...restProps} />
        }
    }
    return React.forwardRef((props,ref) => {
        return <NewComponent {...props} theRef={ref} />
    });
}

const FancyButton = React.forwardRef((props,ref) => {
    const {label,handleClick} = props;
    return <button className='fancyButton' ref={ref} onClick={handleClick}>{label}</button>
})

export default HOC(FancyButton);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值