React基本用法

2021.09/JavaScript

React是用于构建用户界面的JavaScript库,声明式编写UI,数据变动时React能高效更新并渲染合适的组件。

准备工作

使用Create React App创建项目。

npx create-react-app my-app

JSX

JSX标签语法是JavaScript的语法扩展,用来描述UI视图和事件逻辑等。Babel会把JSX转译成React.createElement()函数调用,创建React元素对象。

通过大括号{}嵌入变量或表达式,在属性中嵌入表达式时,不要在大括号外面加引号。

如果一个标签中无内容,可以使用/>闭合标签。

const element = <h1>Hello, {name}</h1>;
const element = <img src={user.avatarUrl} />;

JSX也是表达式,可以在if语句和for循环中使用JSX,将JSX赋值给变量,把JSX当作参数传入,以及在函数中返回JSX。

function getGreeting(user) {
if (user) return <h1>Hello, {formatName(user)}</h1>;
return <h1>Hello, Stranger</h1>;
}

JSX语法上更接近JavaScript而不是HTML,因而使用camelCase(小驼峰)来定义属性名称,class变成了className、for写成htmlFor、tabindex则为tabIndex。

为了便于阅读,将JSX拆分为多行,此时建议将内容包裹在括号()中。

const element = (
<h1 className="welcome">
Hello, {formatName(user)}!
</h1>
);

渲染

将React元素渲染到DOM节点中。

const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('app'));

组件、Props

组件即可复用的代码片段,接受入参props,返回React元素。

组件名称必须以大写字母开头。

函数组件

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

class组件

class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

除了DOM标签,React元素也可以是自定义组件。当React元素为自定义组件时,会将JSX接收的属性(attributes)和子组件(children)合并成一个props对象传递给组件。

State、生命周期

React组件必须像纯函数一样保护其props的只读性。如果需要组件随用户操作、网络响应或者其他变化而动态更改输出内容,可以使用state。

class Clock extends React.Component {
constructor(props) {
// 使用props参数调用父类的构造函数
super(props);
// 在构造函数中为state赋初值,这里是唯一可以给state赋值的地方
this.state = { date: new Date() };
}

tick() {
// 调用setState(),让React知道state已变,React会重新调用render()
this.setState({ date: new Date() });
}

componentDidMount() {
// 可以在class中随意添加不参与数据流的额外字段,例如这里的timeId
this.timeId = setInterval(
() => this.tick(),
1000
);
}

componentWillUnmount() {
clearInterval(this.timeId);
}

render() {
const { date } = this.state;
return <p>{date.toLocaleTimeString()}</p>;
}
}

直接修改state不会重新渲染组件,而应该使用setState()。

出于性能考虑,React可能会把多个setState()调用合并成一个,这样this.props和this.state可能会异步更新。如果更新下一个状态时依赖这些值,需要setState()接收一个函数,这个函数用上一个state作为第一个参数,将此次更新被应用时的props做为第二个参数。

this.setState((state, props) => ({
counter: state.counter + props.increment
}));

组件的生命周期包括组件实例被创建并插入DOM中,组件的props或state发生变化时触发更新,以及组件从DOM中移除的过程。

挂载

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

更新

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

卸载

  • componentWillUnmount()

render()方法

render()方法是class组件中唯一必须实现的方法,有多种返回类型。

  • 返回JSX创建的DOM节点或者自定义组件
  • 使用Fragment返回多个元素
  • 使用Portal将子节点渲染到父组件以外的DOM节点中
  • 将字符串或数字渲染为文本节点
  • 布尔类型或null则什么都不渲染
// 当不需要key或属性时,可使用短语法<>和</>
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}

对话框、悬浮卡以及提示框等场景,使用Portal将组件渲染到指定的DOM中。这样做不会影响事件冒泡。

const modalRoot = document.getElementById('modal-root');

class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}

componentDidMount() {
modalRoot.appendChild(this.el);
}

componentWillUnmount() {
modalRoot.removeChild(this.el);
}

render() {
return ReactDOM.createPortal(
this.props.children,
this.el
);
}
}

事件处理

React事件命名采用camelCase(小驼峰),使用JSX时需传入一个函数用作事件处理。

function Form() {
function handleSubmit(e) {
e.preventDefault();
}

return (
<form onSubmit={handleSubmit}>
<button type="submit">Submit</button>
</form>
);
}

JavaScript中,class的方法默认不会绑定this实例,解决办法有:

  • 在constructor()中使用bind为事件处理函数绑定实例
  • 在class中使用箭头函数定义方法
  • 在回调中使用箭头函数,此时如果该回调函数作为prop传入子组件时,可能会额外重新渲染
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
class ClickButton extends React.Component {
handleClick = () => { console.log(this); };

render() {
return (
<button onClick={this.handleClick}>
Click
</button>
);
}
}
class ClickButton extends React.Component {
handleClick() { console.log(this); }

render() {
return (
<button onClick={() => this.handleClick()}>
Click
</button>
);
}
}

通过箭头函数或者bind为事件处理函数传递额外参数。下面两种情况,事件对象e都会被作为第二个参数传递。使用箭头函数时,事件对象必须显式传递,而bind方式的事件对象会被隐式传递。

<button onClick={e => this.del(id, e)}>Delete</button>
<button onClick={this.del.bind(this, id)}>Delete</button>

Refs

通过Refs访问DOM节点或者React组件实例。

class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 使用React.createRef()创建ref
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}

focusTextInput() {
// 通过ref的current属性访问DOM节点
this.textInput.current.focus();
}

render() {
// 通过ref属性将上面创建的ref附加到input节点
return (
<div>
<input ref={this.textInput} />
<button
onClick={this.focusTextInput}
>Focus the text input</button>
</div>
);
}
}
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}

componentDidMount() {
// 通过ref的current属性访问组件实例
this.textInput.current.focusTextInput();
}

render() {
return <CustomTextInput ref={this.textInput} />;
}
}

上面这种方式不能在函数组件上使用,因为它们没有实例。在函数组件上,可以使用forwardRef进行Refs转发,将ref通过函数组件传递到其子组件上。

const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref}>{props.children}</button>
));

// 中转获取button
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

在函数组件内部使用useRef。

function CustomTextInput(props) {
// 使用useRef创建ref
const textInput = useRef(null);

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

return (
<div>
<input ref={textInput} />
<button
onClick={focusTextInput}
>Focus the text input</button>
</div>
);
}

React也支持另一种设置Refs的方式,称为“回调Refs”。即传递一个函数,在这个函数中接受DOM节点或者组件实例作为参数,以使它们能在其他地方被存储和访问。

class CustomTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = null;
this.setTextInputRef = element => {
this.textInput = element;
};
this.focusTextInput = () => {
if (this.textInput) this.textInput.focus();
};
}

componentDidMount() {
this.focusTextInput();
}

render() {
return (
<div>
<input ref={this.setTextInputRef} />
<button
onClick={this.focusTextInput}
>Focus the text input</button>
</div>
);
}
}

表单

在React中使用表单元素,有“受控组件”或者“非受控组件”两种形式。

“受控组件”自己维护内部state,并根据用户输入等操作进行更新。

“非受控组件”交由DOM节点自己处理,使用ref获取表单数据,可以通过defaultValue属性给表单元素设置初始值。

class InfoForm extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
healthy: true,
gender: 'male',
city: ''
};

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

handleChange({ target }) {
this.setState({
[target.name]: target.type === 'checkbox' ? target.checked : target.value
});
}

handleSubmit(event) {
console.log(this.state);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<input
name="username"
placeholder="用户名"
value={this.state.username}
onChange={this.handleChange}
/>

<label>
是否健康
<input
name="healthy"
type="checkbox"
checked={this.state.healthy}
onChange={this.handleChange}
/>
</label>

<label>
<input
name="gender"
type="radio"
value="male"
checked={this.state.gender === 'male'}
onChange={this.handleChange}
/>男
</label>
<label>
<input
name="gender"
type="radio"
value="female"
checked={this.state.gender === 'female'}
onChange={this.handleChange}
/>女
</label>

<select name="city" value={this.state.city} onChange={this.handleChange}>
<option value="" disabled>请选择城市</option>
<option value="BJ">北京</option>
<option value="SH">上海</option>
</select>

<button type="submit">提交</button>
</form>
);
}
}
京ICP备14036213号-2