如何在 React 中附加到无状态组件的 ref?

我希望创建一个无状态组件,它的 input元素可以由父组件验证。

在下面的示例中,我遇到了一个问题,输入 ref从未被分配给父级的私有 _emailAddress属性。

当调用 handleSubmit时,this._emailAddress就是 undefined。是我遗漏了什么,还是有更好的办法?

interface FormTestState {
errors: string;
}


class FormTest extends React.Component<void, FormTestState> {
componentWillMount() {
this.setState({ errors: '' });
}


render(): JSX.Element {
return (
<main role='main' className='about_us'>
<form onSubmit={this._handleSubmit.bind(this)}>
<TextInput
label='email'
inputName='txtInput'
ariaLabel='email'
validation={this.state.errors}
ref={r => this._emailAddress = r}
/>


<button type='submit'>submit</button>
</form>
</main>
);
}


private _emailAddress: HTMLInputElement;


private _handleSubmit(event: Event): void {
event.preventDefault();
// this._emailAddress is undefined
if (!Validators.isEmail(this._emailAddress.value)) {
this.setState({ errors: 'Please enter an email address.' });
} else {
this.setState({ errors: 'All Good.' });
}
}
}


const TextInput = ({ label, inputName, ariaLabel, validation, ref }: { label: string; inputName: string; ariaLabel: string; validation?: string; ref: (ref: HTMLInputElement) => void }) => (
<div>
<label htmlFor='txt_register_first_name'>
{ label }
</label>


<input type='text' id={inputName} name={inputName} className='input ' aria-label={ariaLabel} ref={ref} />


<div className='input_validation'>
<span>{validation}</span>
</div>
</div>
);
121744 次浏览

The value of your TextInput is nothing more than a state of your component. So instead of fetching the current value with a reference (bad idea in general, as far as I know) you could fetch the current state.

In a reduced version (without typing):

class Form extends React.Component {
constructor() {
this.state = { _emailAddress: '' };


this.updateEmailAddress = this.updateEmailAddress.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}


updateEmailAddress(e) {
this.setState({ _emailAddress: e.target.value });
}


handleSubmit() {
console.log(this.state._emailAddress);
}


render() {
return (
<form onSubmit={this.handleSubmit}>
<input
value={this.state._emailAddress}
onChange={this.updateEmailAddress}
/>
</form>
);
}
}

EDIT: You now can with React Hooks. See the answer by Ante Gulin.

You can't access React like methods (like componentDidMount, componentWillReceiveProps, etc) on stateless components, including refs. Checkout this discussion on GH for the full convo.

The idea of stateless is that there isn't an instance created for it (state). As such, you can't attach a ref, since there's no state to attach the ref to.

Your best bet would be to pass in a callback for when the component changes and then assign that text to the parent's state.

Or, you can forego the stateless component altogether and use an normal class component.

From the docs...

You may not use the ref attribute on functional components because they don't have instances. You can, however, use the ref attribute inside the render function of a functional component.

function CustomTextInput(props) {
// textInput must be declared here so the ref callback can refer to it
let textInput = null;


function handleClick() {
textInput.focus();
}


return (
<div>
<input
type="text"
ref={(input) => { textInput = input; }} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}

You can useuseRef hook which is available since v16.7.0-alpha.

EDIT: You're encouraged to use Hooks in production as of 16.8.0 release!

Hooks enable you to maintain state and handle side effects in functional components.

function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}

Read more in Hooks API documentation

You can also get refs into functional components with a little plumbing

import React, { useEffect, useRef } from 'react';


// Main functional, complex component
const Canvas = (props) => {
const canvasRef = useRef(null);


// Canvas State
const [canvasState, setCanvasState] = useState({
stage: null,
layer: null,
context: null,
canvas: null,
image: null
});


useEffect(() => {
canvasRef.current = canvasState;
props.getRef(canvasRef);
}, [canvasState]);




// Initialize canvas
useEffect(() => {
setupCanvas();
}, []);


// ... I'm using this for a Konva canvas with external controls ...


return (<div>...</div>);
}


// Toolbar which can do things to the canvas
const Toolbar = (props) => {
console.log("Toolbar", props.canvasRef)


// ...
}


// Parent which collects the ref from Canvas and passes to Toolbar
const CanvasView = (props) => {
const canvasRef = useRef(null);


return (
<Toolbar canvasRef={canvasRef} />
<Canvas getRef={ ref => canvasRef.current = ref.current } />
}

This is late but I found this solution much better. Pay attention to how it uses useRef & how properties are available under current property.

function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);


function handleClick() {
textInput.current.focus();
}


return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}

For more reference check react docs