初学React,需要了解哪些知识点?

1,667 阅读6分钟

前言

刚刚接触 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>

特点

  1. 声明式编程中无需使用变量,也不用在执行过程中更新变量的值,简单来说就是避免了创建和修改状态
  2. 声明式编程中只需要知道自己要做什么,无需列出实现效果的所有步骤
  3. 代码简洁、易读、可维护性高

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 将模板放到其所属位置,也就是将模板放到逻辑中,这样的使用方式会比单独使用模板灵活

在模板中 分离逻辑,不会从根本上解决耦合的问题,因为想在不影响其他文件的前提下修改某个文件是很难的

体现出耦合性的几个因素:

  1. 模板高度依赖从逻辑层接收到的数据模型来显示信息,因此对于复杂的数据结构处理不是很友好
  2. JavaScript 操作模板渲染出的 DOM 元素来更新 UI (即使它们是从独立文件中加载的)
  3. 样式也存在同样的问题,它们定义在不同的文件中,但模板引用了样式文件(而且 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
  1. 依赖安装
npm install --save jsx-control-statements

  1. 在.babelrc 中加入如下配置
"plugins": ["jsx-control-statements"]

  1. 常用语句
<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> 
  )
}

总结

通过以上内容,我们可以了解到:

  1. 如何编写声明式代码
  2. 组件与 React 元素的区别
  3. 逻辑和模板放在一起的原因
  4. JSX 的工作原理、与 HTML 的区别以及常用模式

参考资料

  • 《React设计模式与最佳实践》