前言
刚刚接触 React,对于框架的特性、常用的编程风格以及插件都很陌生,希望下面的内容可以帮到初学的小伙伴
1. 声明式编程
首先要理解声明式编程的含义,以及它和命令式编程的区别所在,先来看一个简单的函数实现
传入一个包含大写字符串的数组,返回一个全小写字符串的数组
toLowerCase(['FOO', 'BAR']) // ['foo', 'bar']
- 命令式函数
const toLowerCase = input => {
// 保存结果
const output = []
// 遍历所有元素,并将小写元素 push 到结果数组中
for (let i = 0; i < input.length; i++) {
output.push(input[i].toLowerCase())
}
// 返回结果
return output
}
- 声明式函数
const toLowerCase = input => input.map( value => value.toLowerCase())
实际应用
使用 React组件 来对比一下 命令式和 声明式的区别,这样比较容易理解
展示一个带有标记的地图,使用 Google Maps SDK
- 命令式
// 创建地图
const map = new google.maps.Map(document.getElementById('map'), {
zoom: 4,
center: myLatLng,
})
// 创建标记
const marker = new google.maps.Marker({
position: myLatLng,
title: 'Hello World!',
})
// 在地图中添加标记
marker.setMap(map)
- React 组件
<Gmaps zoom={4} center={myLatLng}>
<Marker position={myLatLng} title="Hello world!" />
</Gmaps>
特点
- 声明式编程中无需使用变量,也不用在执行过程中更新变量的值,简单来说就是避免了创建和修改状态
- 声明式编程中只需要知道自己要做什么,无需列出实现效果的所有步骤
- 代码简洁、易读、可维护性高
2. React 元素
React 使用 元素 这种特殊对象来控制 UI 流程,这些 不可变对象比 组件和 组件实例简单的多,而且其中只包含了展示界面所必须的信息:
{
type: Title,
props: {
color: 'red',
children: {
type: 'h1',
props: {
children: 'Hello, H1!'
}
}
}
}
type
属性告诉 React 如何处理 元素 本身- 字符串类型,代表 DOM 节点
- 函数类型,代表 React 组件
children
属性是可选的,为 元素的直接后代
当 type 为函数类型时,React 会传入 props 取回底层元素,并且对返回结果递归执行相同操作,直到取回完整的 DOM 节点树,简单的代码实现大概是这个样子:
/**
* 创建 Element
* @param {*} type element类型: ElementWrapper or Component
* @param {*} attributes element属性: attributes or props
* @param {...any} children 子元素
*/
export const createElement = (type, attributes, ...children) => {
let element
typeof type === 'string'
? (element = new ElementWrapper(type))
: (element = new type())
// setProps
for (let p in attributes) {
element.setProps(p, attributes[p])
}
const insertChildren = children => {
for (let child of children) {
// text content
if (typeof child === 'string') {
child = new TextWrapper(child)
}
// element
if (typeof child === 'object' && child instanceof Array) {
insertChildren(child)
} else {
element.appendChild(child)
}
}
}
insertChildren(children)
return element
}
简易版 react 代码实现:点击传送至代码仓库
3. 逻辑与模板结合
React 将模板放到其所属位置,也就是将模板放到逻辑中,这样的使用方式会比单独使用模板灵活
在模板中 分离逻辑,不会从根本上解决耦合的问题,因为想在不影响其他文件的前提下修改某个文件是很难的
体现出耦合性的几个因素:
- 模板高度依赖从逻辑层接收到的数据模型来显示信息,因此对于复杂的数据结构处理不是很友好
- JavaScript 操作模板渲染出的 DOM 元素来更新 UI (即使它们是从独立文件中加载的)
- 样式也存在同样的问题,它们定义在不同的文件中,但模板引用了样式文件(而且 CSS 选择器也遵循了文档标记结构)
4. CSS in JavaScript
使用 Styled Component 库
- 可以用样式化函数创建任何元素,形式如 styled.elementName,其中 elementName 可以是 div、按钮或者任何有效的其他 DOM 元素
- 函数可以接收包括所有 JavaScript 表达式的模板字面量
npm install --save styled-components
下面的例子返回普通的 React 组件 Button,它渲染了一个按钮元素,并加上了模板中定义的样式(先创建唯一的类名,再将它加到元素上,最后向页面文档头部注入相应的样式)
import styled from 'styled-components'
const Button = styled.button`
background-color: #ff0000;
width: 320px;
padding: 20px;
border-radius: 5px;
border: none;
outline: none;
&:hover {
color: #fff;
}
&:active {
position: relative;
top: 2px;
}
@media (max-width: 480px) {
width: 160px;
}
`
Styled Component 几乎支持所有的 CSS 特性,以及类似 Sass/Less 语法,了解更多特性可以 移步官网
5.JSX
React 可以是用 React.createElement 定义元素,也可以使用 JSX ( Babel 将 JSX 转义成 JavaScript 函数)
元素创建形式
- 普通元素创建
React.createElement('div')
// JSX
<div />
- 渲染 HTML 元素与组件
// 渲染 HTML button 元素,传入字符串
React.createELement('button')
// JSX
<button />
// 渲染 Button 组件,传入组件本身
React.createElement(Button)
// JSX
<Button />
- 携带属性
// 携带属性
React.createElement('img', {
src: require('./logo.svg'),
alt: 'React.js',
})
// JSX
<img src={require('./logo.svg')} alt='React.js' />
- 包含子元素
// 包含子元素
React.createElement(
"div",
null,
React.createElement(
"a",
{ href: "https://facebook.github.io/react/" },
"Click me!"
)
);
// JSX
<div>
<a href="https://facebook.github.io/react/">Click me!</a>
</div>
- JSX 使用函数或变量
<div>
<a
className={className}
href={this.makeHref()}
>
Click me!
</a>
</div>
与HTML的区别
- 属性:JSX 不是一门标准语言,需要转译成 JavaScript;由于这一点,有些属性无法使用比如,我们需要用 className 取代 class,用 htmlFor 取代 for (
class
和for
是 JavaScript 保留字) :
<label className="awesome-label" htmlFor="name" />
- 样式:样式属性期望传入 JavaScript 对象,而不是 CSS 字符串,而且样式名的写法为驼峰式命名法
<div style={{ backgroundColor: 'red' }} />
- 根元素:因为 JSX 元素会转换为 JavaScript 函数,JavaScript 不允许返回两个函数,因此如果有多个同级元素,需要强制将它们封装在一个父元素中
// Parsing error: Adjacent JSX elements must be wrapped in an enclosing tag
<div />
<div />
// 正确写法
<div>
<div />
<div />
</div>
- 空格:JSX 处理文本和元素间的空格的方式和 HTML 不同
// 页面中渲染为:foobarbaz
// 因为嵌套的三行代码转译成了 div 元素的 独立子元素,没有将空格计算在内
<div>
<span>foo</span>
bar
<span>baz</span>
</div>
// 页面中渲染为:foo bar baz
<div>
<span>foo</span>
{' '}
bar
{' '}
<span>baz</span>
</div>
// or
<div>
<span>foo</span> bar <span>baz</span>
</div>
- 布尔值属性:JSX 中属性没有设置默认值时,默认为 true,类似于 HTML 中的 disabled
<button disabled />
React.createElement("button", { disabled: true });
<button disabled={false} />
React.createElement("button", { disabled: false });
常用模式
- 展开属性
const foo = { id: 'foo', className: 'bar' }
return <div {...foo} />
- 条件语句
<div>
{isLoggedIn ? <LogoutButton /> : <LoginButton />}
</div>
- 辅助函数
canShowSecretData() {
const { dataIsReady, isAdmin, userHasPermissions } = this.props
return dataIsReady && (isAdmin || userHasPermissions)
}
<div>
{this.canShowSecretData() && <SecretData />}
</div>
- getter 配合计算属性
get price() {
return `${this.props.currency}${this.props.value}`
}
<div>{this.price}</div>
- 循环
<ul>
{users.map(user => <li>{user.name}</li>)}
</ul>
- 使用 jsx-control-statements
- 依赖安装
npm install --save jsx-control-statements
- 在.babelrc 中加入如下配置
"plugins": ["jsx-control-statements"]
- 常用语句
<If condition={this.canShowSecretData}>
<SecretData />
</If>
<Choose>
<When condition={...}>
<span>if</span>
</When>
<When condition={...}>
<span>else if</span>
</When>
<Otherwise>
<span>else</span>
</Otherwise>
</Choose>
<ul>
<For each="user" of={this.props.users}>
<li>{user.name}</li>
</For>
</ul>
- 次级渲染:当渲染方法的代码量多到难以维护时,为了保持渲染方法简洁,将其拆分成更小的方法,同时又将所有逻辑都保留在原有组件内部
renderUserMenu() {
// JSX 用于用户菜单
}
renderAdminMenu() {
// JSX 用于管理员菜单
}
render() {
return (
<div>
<h1>Welcome back!</h1>
{this.userExists && this.renderUserMenu()}
{this.userIsAdmin && this.renderAdminMenu()}
</div>
)
}
总结
通过以上内容,我们可以了解到:
- 如何编写声明式代码
- 组件与 React 元素的区别
- 逻辑和模板放在一起的原因
- JSX 的工作原理、与 HTML 的区别以及常用模式
参考资料
- 《React设计模式与最佳实践》