React 基础入门
React 是前端主流框架之一,以其出色的性能和完善的生态著称。本文介绍 React 的核心概念与开发规范。
JSX 语法
React 使用独特的 JSX 语法,在组件中插入类似 HTML 的语法,简化创建视图的流程。
React 基础入门涵盖 JSX 语法、组件创建方式(Class 与函数)、数据流机制(State 与 Props)、事件处理、DOM 操作及表单控制。重点讲解单向数据流原理、父子组件通信、跨级 Context 使用以及受控与非受控组件的区别。通过代码示例演示 setState 异步更新、合成事件绑定及 dangerouslySetInnerHTML 安全用法,适合初学者快速掌握 React 核心概念与开发规范。
React 是前端主流框架之一,以其出色的性能和完善的生态著称。本文介绍 React 的核心概念与开发规范。
React 使用独特的 JSX 语法,在组件中插入类似 HTML 的语法,简化创建视图的流程。
ReactDOM.render(( <div> <h1>标题</h1> </div> ), document.getElementById('root'))
通过简单的语法页面就会被插入一个 div 和一个 h1 标签。原生的 HTML 元素可以被直接使用。以上的语法并不是 JS 支持的语法,需要被转换之后才能运行。
React 的强大之处在于可以自定义组件,实现组件的复用。
import * as React from 'react'
class Page extends React.Component {
render() {
return (<div> home111 © © \ua9 </div>)
}
}
ReactDOM.render(( <div> <Page/> </div> ), document.getElementById('root'))
我们定义了一个 Page 组件,可以在 JSX 里面像调用 HTML 一样直接调用。
let name = 'hi'
ReactDOM.render(( <div> {name} </div> ), document.getElementById('root'))
使用 {} 就可以插入数据,但是 {} 中间的必须是 JS 表达式,不能是语句。如果表达式的执行结果是一个数组,则会自动 join。
JSX 语法和 HTML 语法一样,也是可以插入注释,只不过写的时候有一些区别。
let name = 'hi'
ReactDOM.render(( <div> {/* 注释 */} {name} </div> ), document.getElementById('root'))
在子组件中插入注释,需要使用 {} 包裹起来,在 // 之间插入注释文字。
let name = 'hi'
ReactDOM.render(( <div> {name} <img /* 多行注释 */ src="1.jpg"/> </div> ), document.getElementById('root'))
在标签中间,可以插入一个多行注释。
let name = 'hi'
ReactDOM.render(( <div> <img src="1.png"/> </div> ), document.getElementById('root'))
let props = { src: '1.png', alt: '1 图片' }
ReactDOM.render(( <div> <img src={"1.png"}/> <img {...props}/> </div> ), document.getElementById('root'))
ReactDOM.render(( <div className="tab"> <label htmlFor="name">姓名:</label><input id="name"/> </div> ), document.getElementById('root'))
ReactDOM.render(( <div className="tab"> <input type="text" required/> <input type="text" required={true}/> </div> ), document.getElementById('root'))
ReactDOM.render(( <div className="tab"> <input type="text" data-init="22"/> </div> ), document.getElementById('root'))
如果动态的插入 HTML 元素,React 出于安全性考虑会自动帮我们转义。所以一定要动态的插入元素的话,使用 dangerouslySetInnerHTML。
ReactDOM.render(( <div className="tab"> <div dangerouslySetInnerHTML={{__html: '<span>test</span>'}}></div> </div> ), document.getElementById('root'))
这是旧版本的 API,使用 React.createClass 创建组件,配套的一些 API,有 getDefaultProps, getInitialState。官方已经不建议使用了,使用下面新的 API 替代。
import * as React from 'react'
class Page extends React.Component {
render() {
return (<div> home </div>)
}
}
这是一个实现了 render 方法的 class。也是一个基本的 React 组件。
function Button(props, context) {
return (
<button>
<em>{props.text}</em>
<span>{context.name}</span>
</button>
);
}
纯函数,不存在 state,只接受 props 和 state。纯函数有优点,优点就是易于测试,无副作用。
State 是组件的内部状态,需要在视图里面用到的状态,才需要放到 State 里面去。如下,我们在类上创建一个 state 属性,在视图里面通过使用 this.state.name 去引用。而这里的 state 定义则代替的是 getInitialState 方法。
import * as React from 'react'
class Page extends React.Component {
state = { name: '小明' }
render() {
return (<div> {this.state.name} </div>)
}
}
如何更新 state 呢?直接更改 state 其实是可以的,不过这样子无法触发组件视图的更新机制。所以使用 setState() API。值得注意的是 setState 是异步的,原因是 React 内部需要对 setState 做优化,不是 state 变了立刻去更新视图,而是拦截一部分 state 的改变,等到合适的时机再去更新视图。
import * as React from 'react'
class Page extends React.Component {
state = { name: '小明' }
render() {
setTimeout(() => this.setState({ name: '小明儿子' }), 5000)
return (<div> {this.state.name} </div>)
}
}
真实开发中绝不要在 render 函数里面去更改 state,以上只是为了演示
Props 是组件之间传递数据的最主要 API,React 推崇的是自顶向下的数据流向,也就是组件的数据要从父组件传给子组件。如果子组件需要向父组件传递数据,则需要使用回调函数的方式。
import * as React from 'react'
class Child extends React.Component {
render() {
return (<div> {this.props.parentName} </div>)
}
}
class Parent extends React.Component {
state = { name: '小明' }
render() {
setTimeout(() => this.setState({ name: '小明儿子' }), 5000)
return (<div> <Child parentName={this.state.name}/> </div>)
}
}
可以看到 Child 组件显示了父组件的 name。当父组件状态更新了,子组件同步更新。那如何在子组件中更改父组件状态呢?答案是回调函数。
import * as React from 'react'
class Child extends React.Component {
update() {
this.props.onChange('小明名字改了')
}
render() {
return (<div> {this.props.parentName} <button onClick={this.update.bind(this)}>更新</button> </div>)
}
}
class Parent extends React.Component {
state = { name: '小明' }
changeName(name) {
this.setState({ name })
}
render() {
setTimeout(() => this.setState({ name: '小明儿子' }), 5000)
return (<div> <Child onChange={this.changeName.bind(this)} parentName={this.state.name}/> </div>)
}
}
注意:Props 是不可以更改的,这既不符合 React 单向数据流思想,也为维护带来灾难。
React 里面的用户事件都是合成事件,被 React 封装过。内部使用的还是事件的委托机制。 常用的事件有点击事件 onClick,input 的 onChange 事件等,官网都可以查到。
合成事件的 this 指向问题 就像上文一样,我们绑定事件的方式很奇怪,使用了 bind 来显示绑定 this 的指向。因为传递到组件内部的只是一个函数,而脱离了当前对象的函数的 this 指向是不能指到当前组件的,需要显示指定。
通过 bind
<button onClick={this.update.bind(this)}>更新</button>
构造器内部指定
import * as React from 'react'
class Child extends React.Component {
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update() {
this.props.onChange('小明名字改了')
}
render() {
return (<div> {this.props.parentName} <button onClick={this.update}>更新</button> </div>)
}
}
箭头函数
import * as React from 'react'
class Child extends React.Component {
update = (e) => {
this.props.onChange('小明名字改了')
}
render() {
return (<div> {this.props.parentName} <button onClick={this.update}>更新</button> </div>)
}
}
装饰器
import * as React from 'react'
class Child extends React.Component {
constructor(props) {
super(props)
}
@autoBind
update() {
this.props.onChange('小明名字改了')
}
render() {
return (<div> {this.props.parentName} <button onClick={this.update}>更新</button> </div>)
}
}
装饰器是 ES7 语法,如果需要使用需要安装对应的 babel preset 版本。而 TypeScript 则原生支持。
autoBind 原理大概就是劫持 get 方法,get 时改变 this 指向
通过 e.nativeEvent 获取原生事件对象。
import * as React from 'react'
class Child extends React.Component {
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
console.log(e.nativeEvent)
}
render() {
return (<div> <button onClick={this.update}>更新</button> </div>)
}
}
e.preventDefault() // 取消默认行为
e.stopPropagation() // 取消冒泡
这个和浏览器原生事件处理方案是一致的。问题是我们只可以调合成事件的 e 的方法,不可以通过 e.nativeEvent 方法做这些操作,原因是上文讲过的委托。
特殊的 props,ref 组件对象的引用,现在官方也不建议直接给 ref 赋值,需要通过函数来赋值。
ReactDOM.render(( <div> <Calendar ref={ref => this.c = ref} any-ss="text"/> </div> ), document.getElementById('root'))
顶层 API,只有在根组件时候才需要使用。第一个参数是 Component,第二个参数是 dom 节点。
通过传入 component 实例获取此 component 根 dom 节点,在这里可以去 dom 节点进行操作了,虽然极其不建议这么做,但是你确实可以做。
卸载此组件,并销毁组件 state 和事件。 接收组件的引用,也就是 ref。仅仅是取消挂载,组件还在,如果需要彻底清除的话,需要手动删掉此 dom。
与 Vue 框架不同的是,React 如果要实现表单元素变化,状态同步更新,必须要自己去监听表单事件。
import * as React from 'react'
class Child extends React.Component {
state = { name: '小明' }
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
this.setState({ name: e.target.value })
}
render() {
return (<div> <input onChange={this.update} value={this.state.name}/> </div>)
}
}
受控组件和非受控组件这些都是指的表单组件,当一个表单的值是通过 value 改变的而不是通过 defaultValue 是受控组件,否则就是非受控组件。
下面组件中的 input 就是受控组件。
import * as React from 'react'
class Child extends React.Component {
state = { name: '小明' }
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
this.setState({ name: e.target.value })
}
render() {
return (<div> <input onChange={this.update} value={this.state.name}/> </div>)
}
}
下面组件中的 input 是非受控组件。
import * as React from 'react'
class Child extends React.Component {
state = { name: '小明' }
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
this.setState({ name: e.target.value })
}
render() {
return (<div> <input onChange={this.update} defaultValue={this.state.name}/> </div>)
}
}
父子之间通讯又分为父->子,子->父。
因为 React 单向数据流向的缘故,父->子通信的话直接通过 props。父组件数据变动,直接传递给子组件。
子->父组件之间就要通过回调函数来通信了,父组件传递一个回调函数给子组件,子组件通过调用此函数的方式通知父组件通信。
React 为了实现祖先组件和后辈组件之间的通信问题,引入了 Context API。
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = { color: React.PropTypes.string };
class Message extends React.Component {
render() {
return (
<div>
{this.props.text}
<Button>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
getChildContext() {
return {color: "purple"};
}
render() {
const children = this.props.messages.map((message) => <Message text={message.text} />);
return <div>{children}</div>;
}
}
MessageList.childContextTypes = { color: React.PropTypes.string };
MessageList 中的 color 会自动更新到儿孙组件里面去,实现跨级通信。如果需要反过来通信,则需要借助其他工具,比如事件系统 (Pub/Sub)。
组件之间通信最主流的两种方式脱胎于观察者模式和中介者模式这两种。
跨级之间通信现在最主流的方式就是观察者模式的实现 Pub/Sub,React 社区中的 Redux 也是使用这种方式实现的。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online