分享个人 Full-Stack JavaScript 项目开发经验
本文章是博主跟据自己的开发经验、相关书籍以及React官方说明文档的归纳,帮助理解 React 的核心概念和使用技巧等入门知识。
React 使用声明式库保持DOM和数据同步,组件逻辑使用 JavaScript 而不是模板或指令编写。因为组件可以接受任意元素,包括基本数据类型、React 元素或函数,所以可通过应用程序传递丰富的数据,并将应用程序状态保持在 DOM 之外。
函数式编程贯穿了整个 React 应用的构建,了解的其基本概念十分重要。若还未理解相关概念,可先阅读JavaScript的函数式编程。
浏览器通过 HTML 构造 DOM(文档对象模型)。JavaScript 通过 DOM API 和浏览器交互并修改 DOM。JavaScript 更新或者修改已经渲染过的 DOM 相对容易一些,但是插入新元素的过程非常低效。
采用 React 后,我们不需要直接跟 DOM API 打交道,而是跟一个虚拟 DOM 交互,让 React 构造 UI 或者和浏览器交互。当应用程序状态发生改变时,ReactDOM.render 会尝试以最小的代价仅更新改变了的部分。
虚拟 DOM 由 React 元素组成,它们实际上是 JavaScript 对象。React 元素表示一组操作指令,React 将根据这些指令在浏览器中构建 UI 界面。这样做,可以将数据和 UI 有效隔离。
React.createElement('ul', { className: 'list', 'data-role': 1 },
React.createElement('li', { key: 1 }, 'Lucy'),
React.createElement('li', { key: 2 }, 'Tom')
)
从上面是 React 通过 React.createElement( type, [props], [...children] ) 方法构造 React 元素树的一个简单例子。
为了避免冗长的输入,Facebook 在发布 React 同时也发布了JSX。它是一种 JavaScript 语法扩展,允许用户使用类似 HTML 语法定义 React 元素,进而构造虚拟 DOM。通过 Babel 转译后,JSX 会被转换成 React.createElement() 的方法调用。
JSX:
const ele = <h1><span>hello</span></h1>;
转译后:
var ele = React.createElement(
"h1",
null,
React.createElement(
"span",
null,
"hello"
)
);
使用 JSX 时需要注意:
JSX 组件首字母大写,小写字母开头的标签被认为是 HTML 原生标签。
React 元素属性与 HTML DOM 元素属性的差异
无论是直接通过 React.createElement 或者通过 JSX 声明的 React 元素,它本质上都是一个 JavasScript 对象。因此在编写代码时候要注意React 元素和 HTML DOM 元素属性上的差异。了解详细差异请点击这里。
JSX 组件可以接受字符串或者 JavaScript 表达式作为参数
在 JSX 属性中,字符串使用 "" 定义,JavasScript 表达式通过 {} 定义。
JSX 组件可以嵌套,并且必须闭合或自闭合。
如果没有子代,JSX 可以使用自闭合标签,但不能省略 / ,这也是它与 html5 标签规范的区别之一。
<div className="sidebar" />
JSX 本身也是一种表达式
这意味着它可以赋值给变量、作为函数的参数或返回值等,还可以把它集成到 JavaScript 函数内部,或作为组件属性传递它们等等。
const Tips = (data) => data ? <p>{data.text}</p> : null
const App = <Page left={<Sidebar />} ></Page>
JSX 的数组映射
可以将 {} 中的一个数组映射为 JSX 元素。
<ul>
{
this.props.items.map( (item, index) => <li key={index}>{item}</li> )
}
</ul>
要了解 JSX 的其它使用小技巧可以点击这里。
const items = ['Lucy', 'Tom'];
通过 React.createClass 定义。
React.createClass({
displayName: 'MyList',
renderLi(item, index) {
return React.createElement('li', { key: index }, item)
},
render() {
return React.createElement('ul', { className: 'list', 'data-role': 1 },
this.props.items.map(this.renderLi)
)
}
})
通过 ES6 继承 React.Component 抽象类定义。
class MyList extends React.Component {
renderLi(item, index) {
return React.createElement('li', { key: index }, item)
}
render() {
return React.createElement('ul', { className: 'list', 'data-role': 1 },
this.props.items.map(this.renderLi)
)
}
}
通过无状态函数定义。
const MyList = ({items}) =>
React.createElement('ul', { className: 'list', 'data-role': 1 },
items.map( (item, index) =>
React.createElement('li', { key: index }, item)
)
)
一般地,React 组件的状态由自身管理,并由自身回调方法更新,进而更改 UI。这些状态不会在组件之间共享,也不应该尝试在不同组件间同步状态。为了保持数据流是自上而下的,组件间要共用状态采用的方式是状态提升。
状态提升即把不同组件间需要共用的状态交由它们最近的父组件控制。组件间共用的状态和状态更新方法都通过不可变的 props 由父组件传递。之前组件的状态管理里和更新方法均提升到父组件中完成。这时当父组件更新状态时,各子组件可同时更新。
共用状态通过的组件层数越多,需要编写的“模板代码”就会越多,以实现状态的逐层传递。但它的好处是能把子组件变得更可预测,状态控制的范围缩小到父组件,容易了解你的程序是如何运行的,减少发生 bug 的地方。
React 允许我们通过组合模型实现组件的复用和实例化。
function Header(props) {
return (
<header>
<h1>{props.title}</h1>
{props.children}
<h1>Something else.</h1>
</header>
);
}
function WelcomeHeader(){
return (
<Header title="hello">
<p>It's a nice day.</p>
</Header>
);
}
以上例子,<Header> JSX 标签内的任何内容都将通过 children 属性传递,另通过其它配置属性来实例化通用组件。
事件处理
React 事件绑定属性的命名采用驼峰式写法,在 JSX 中传入一个函数作为事件处理函数。事件处理函数只能通过 preventDefault 而不是 false 阻止默认行为。事件处理函数的 this 绑定推荐以下两种方式。
在构造函数中绑定:
class Header extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
console.log(e.target.id);
}
render() {
return (<header id="1" onClick={this.handleClick}></header>);
}
}
使用属性初始化器语法的箭头函数:
class Header extends React.Component {
handleClick = (e) => {
console.log(e.target.id);
}
render() {
return (<header id="1" onClick={this.handleClick}></header>);
}
}
条件渲染
JSX 表示的是一个 JavaScript 对象,所以它可以结合 JavaScript 的流程控制语句、逻辑运算符和三目运算等进行条件渲染。
元素列表
基于数据的元素列表,为了帮助 React 最小化更新 DOM,需要给这些元素添加 key 属性,使这些兄弟元素之间有唯一的 key。属性 key 为 React 的保留属性名称。
表单元素
在 React 中,界面被视为一个特定时刻的固定内容。跟双向数据绑定不一样,它不是随时处于变化之中的。
在 HTML 中像 <input>、<textarea> 和 <select> 这类表单元素会维持自身状态,并根据用户输入进行更新。React 可以以“受控组件”或“非受控组件”两种形式渲染表单元素。表单数据值由 React 控制的输入表单元素称为“受控组件”,由 DOM 处理时为“非受控组件”(更多的使用在要与其它非 React 代码集成)。其中 <input type="file"> 始终为非受控组件,它的值只能由用户设定。
示例:
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
mySelect: 2,
myTextarea: 2
};
}
handleChange = (e) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit = (e) => {
e.preventDefault();
console.log(this.input.value, this.state.mySelect, this.state.myTextarea);
}
render() {
return(
<form onSubmit={this.handleSubmit}>
<label>
Select:
<select value={this.state.mySelect} name="mySelect" onChange={this.handleChange}>
<option value="1">Tom</option>
<option value="2">Lucy</option>
</select>
</label>
<label>
Textarea:
<textarea value={this.state.myTextarea} name="myTextarea"
onChange={this.handleChange} />
</label>
<label>
Input:
<input defaultValue="Amy" type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
以上例子中的 select 和 textarea 使用了“受控组件”形式,注意到其值的设置方式与 html 中的并不一样,而是更像 input:text 的设置方式。例子中的 input 使用了“非受控组件”形式,初始值通过 defaultValue 设置,DOM 元素通过ref属性的函数参数返回。
了解有关使用类组件特殊属性propTypes和defaultProps进行 props 属性值的类型校验和初始值设置的说明,可以点击这里。
了解 React 组件更新的协调算法及keys的使用注意事项,可以点击这里。
了解如何通过Context上下面传递全局数据,可以点击这里。
了解如何使用React.Fragment为一个组件返回多个元素,可以点击这里。
了解如何使用ReactDOM.createPortal(child, container)将子节点渲染到父组件以外的 DOM 节点,但该组件仍存在于 React 树中,并正常进行事件冒泡等,可以点击这里。
了解如果使用componentDidCatch(error, info)周期函数设置错误边界类组件捕获子组件的错误,从而避免 React 因遇到未捕错误卸载整个 React 组件树,提高用户体验,可以点击这里。
了解如何使用高阶组件重用组件逻辑及其注意事项,可以点击这里。
了解如何使用render prop把函数作为 prop 值实现 React 组件间共享行为或状态,可以点击这里。
了解 React 如何与第三方UI库或处理数据流的框架协同技巧,可以点击这里。
了解关于 React Web 应用可访问性(Accessibility)的相关标准和检测等,可以点击这里。
了解有关 React Web 应用的代码分割的相关说明,可以点击这里。
了解有关使用React.StrictMode在开发阶段检查不安全生命周期组件、意外副作用的说明,可以点击这里。