React 和 jQuery 的对比:
React是一个聪明的建筑工人,而jQuery是一个比较傻的建筑工人,开发者就是一个建筑的设计师。
- 如果是jQuery为你工作,你不得不事无巨细地告诉jQuery“如何去做”,要告诉他这面墙要拆掉重建,那面墙上要新开一个窗户
- 如果是React为你工作,你所要做的就是告诉这个工人“我想要什么样子”,只要把图纸递给React,他就会替你搞定一切。
当然他不会把整个建筑拆掉重建,而是很聪明地把这次的图纸和上次的图纸做一个对比,发现不同之处,然后只去做适当的修改就完成任务了。
jQuery:
选中一些DOM元素的,然后对这些元素做一些操作
- 根据CSS规则找到特定的DOM元素,给按钮或者文本框绑定监听事件
- 如果事件触发则开始判断,各个DOM对象的当前状态(通常会借助css class来表示状态)
- 改变对应的DOM来实现想要达到的效果
jQuery项目逐渐变得庞大时,写出的代码往往互相纠缠,难以维护。
React:
“我想要显示什么”,而不用操心“怎样去做”
React的理念,归结为一个公式,就像下面这样:(声明式编程)
UI=render(data)
render**()** 函数是一个纯函数,输出完全依赖于输入的函数,两次函数调用如果输入相同,得到的结果也绝对相同。
我们只需要维护自己的data,当data改变时,React可以根据最新的data去渲染我们的UI界面;
React实践的是 “响应式编程”(Reactive Programming)的思想
React中,无论何种事件,引发的都是React组件的重新渲染,至于如何只修改必要的DOM部分,则完全交给React去操作。
React开发依赖:
开发React必须依赖三个库:
- react:包含react所必须的核心代码
- react-dom:react渲染在不同平台所需要的核心代码
- web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
- native端:react-dom会将jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)
- babel:将jsx转换成React代码的工具
ReactDOM.render(
{message}
, document.getElementById(“app”))
<body><div id="app"><button class="change-btn">改变文本</button></div><script src="react.development.js"></script><script src="react-dom.development.js"></script><script src="babel.min.js"></script><script type="text/babel">// 将数据定义到变量中let message = "Hello World";// 通过ReactDom对象来渲染内容ReactDOM.render(<h2>{message}</h2>, document.getElementById("app"));</script></body>
JSX :
为什么使用 JSX?
React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如:
- 比如UI需要绑定事件(button、a原生等等);
- 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI
JSX语法的优点
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它的类型是安全的,在编译过程中就能发现错误。
- 防注入攻击,所有的内容在渲染之前都被转换成了字符串,可以有效地防止 XSS(跨站脚本) 攻击。
JSX的书写规范:
- JSX的顶层只能有一个根元素,所以很多时候会在外层包裹一个div原生;
- JSX中的标签可以是单标签,也可以是双标签;
- 注意:如果是单标签,必须以/>结尾;
JSX的本质
JSX语法:
大括号{ }语法:
注释
<div>{/* 我是一段注释 */}<h2>Hello World</h2></div>
{}中嵌入变量
情况一:当变量是
Number、String、Array类型时,可以直接显示;情况二:当变量是
null、undefined、Boolean类型时,内容为空;- 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
- 如toString方法、和空字符串拼接,String(变量)等方式;
- 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
情况三:
对象类型不能作为子元素(not valid as a React child)
**
- 嵌入表达式
- 运算表达式
- 三元运算符
- 执行一个函数
class App extends React.Component {constructor(props) {...}render() {return (<div>{/* 运算表达式 */}<h2>{this.state.firstName + " " + this.state.lastName}</h2>{/* 三元运算符 */}<h2>{this.state.age >= 18 ? "成年人": "未成年人"}</h2>{/* 执行一个函数 */}<h2>{this.sayHello("kobe")}</h2></div>)}sayHello(name) {return "Hello " + name;}}
属性绑定
常规属性:
属性名 = { }- 如
title、src、href…
- 如
特殊属性:
class: 使用className替代className = { ... }
style内联样式:- style后面跟的是一个对象类型,
KV是样式的属性名和属性值; - 属性名使用驼峰标识,而不是连接符 -;
- style后面跟的是一个对象类型,
如:style={{fontSize: "30px", color: "red", backgroundColor: "blue"}}
label标签的for属性:htmlFor
事件绑定
<button onClick={handleClick}>click me</button>
- 事件的命名采用小驼峰式(camelCase),而不是纯小写;
- 通过
{}传入一个函数作为事件处理函数,这个函数会在事件发生时被React调用执行; - 使用函数时不能加括号,不然会直接执行。
this绑定问题
class Foo extends React.Component {handleClick () {this.setState({ xxx: aaa }); /*Cannot read property 'state' of undefined*/console.log(this) /*undefined*/}render() {return (<button onClick={this.handleClick}>Click me</button>)}}
这里会报 this 是 undefined 的错。官网的事件处理有下面一段话:
必须谨慎对待 JSX 回调函数中的
this,在 JavaScript 中,class 的方法默认不会绑定this。 如果忘记绑定this.handleClick并把它传入了onClick,当调用这个函数的时候this的值为undefined。 这并不是 React 特有的行为。通常情况下,如果没有在方法后面添加(),例如onClick={this.handleClick},应该为这个方法绑定this。
这里传递给onClick的,仅仅是 this.handleClick的定义,没有调用,故其this并不会指向类组件这个对象。至于为什么this会为undefined,这和React事件执行机制有关,还未搞明白,后续补充。
React 是如何处理事件的?
React 的事件是合成事件
- React 在组件加载(
mount)和更新(update)时,将事件通过addEventListener统一注册到document上; - 有一个事件池存储了所有的事件,当事件触发的时候,通过
dispatchEvent进行事件分发。 - 最终
this.handleClick会作为一个回调函数调用。
为什么就会丢失 this。**
在函数内部,
this的值取决于函数被调用的方式。
在类组件的render 函数中, 做了类似这样的操作:
class Foo {sayThis () {console.log(this); // 这里的 `this` 指向谁?}exec (cb) {cb();}render () {this.exec(this.sayThis); // 这里并未调用,只是进行了传值// this.sayThis(); -> 直接调用:Foo {}}}var foo = new Foo();foo.render(); // undefined
为什么React没有自动的把 bind 集成到 render 方法中呢?
如下述这样:
class Foo {sayThis () {console.log(this); // 这里的 `this` 指向谁?}exec (cb) {cb().bind(this);}render () {this.exec(this.sayThis);}}var foo = new Foo();foo.render(); // undefined
因为 render 多次调用每次都要 bind 会影响性能,
所以官方建议自己在 `constructor` 中手动 bind 达到性能优化。**
解决this绑定的问题
这里主要针对类组件中的this绑定。而在函数组件中,不用绑定 this ,可以直接调用。
1. **事件那里**直接 **bind this**
class Foo extends React.Component {handleClick () {this.setState({ xxx: aaa })}render() {return (<button onClick={this.handleClick.bind(this)}>Click me</button>)}}
优点:写起来顺手,逻辑顺畅
缺点:**
- 性能不太好,这种方式跟 React 内部帮你 bind 一样的,每次
**render**都会进行 bind, - 如果有两个元素的事件处理函数式同一个,也还是要进行 bind,这样会多写点代码,而且进行两次 bind,性能不是太好。
2. **constuctor** 手动 bind 型
class Foo extends React.Component {constuctor(props) {super(props)this.handleClick = this.handleClick.bind(this)}handleClick () {this.setState({ xxx: aaa })}render() {return (<button onClick={this.handleClick}>Click me</button>)}}
优点:**
- 因为构造函数只执行一次,那么只会
bind一次, - 如果有多个元素都需要调用这个函数,也不需要重复 bind,基本上解决了第一种的两个缺点。
缺点:没有明显缺点
3. 箭头函数型
class Foo extends React.Component {handleClick () {this.setState({ xxx: aaa })}render() {return (// 此语法确保 `handleClick` 内的 `this` 已被绑定// 注意:这里的handleClick有括号<button onClick={(e) => this.handleClick(e)}>Click me</button>)}}
优点:顺手,好看。
缺点:**每次 render 都会创建不同的回调函数,性能会差一点。
在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。
4. public class fields 型(推荐)
这种 class fields还处于实验阶段,目前还没有被纳入标准
- 使用了
箭头函数,所以在当前函数中的this会去上一个作用域中查找; - 而上一个作用域中的this就是当前的对象
class Foo extends React.Component {handleClick = () => {this.setState({ xxx: aaa })}render() {return (<button onClick={this.handleClick}>Click me</button>)}}
优点:好看,性能好。
缺点:**没有明显缺点,需要 babel 插件来支持这种语法。
条件渲染
1. if / else
逻辑上很清晰,但是会存在一些问题,如:重复代码会重新渲染,render 方法过于臃肿。
class App extends React.Component {// ...render() {if (this.state.isLogin) {return (<div className="container-login-register"><Login /></div>)} else {return (<div className="container-login-register"><Rejister /></div>)}}}
2. 变量
使用变量来存储元素,这样可以有条件地渲染组件的部分,剩余部分保持不变。
class App extends React.Component {// ...render() {let elementif (this.state.isLogin) {element = <Login />} else {element = <Rejister />}return (<div className="container-login-register">{element}</div>)}}
3. 三元运算符
适用于两个组件二选一的渲染,当然也可以多层嵌套,但不推荐使用。
class App extends React.Component {// ...render() {return (<div className="container-login-register">{this.state.isLogin ? <Login /> : <Rejister />}</div>)}}
4. 行内条件渲染与运算符 &&
适用于一个组件有无的渲染, true && component 此时会渲染 component。
class App extends React.Component {// ...render() {return (<div className="container-login-register">{this.state.isLogin && <Login />}{!this.state.isLogin && <Rejister />}</div>)}}
5. 返回 null 阻止渲染
如果我们只想在登录时显示登录状态,登录了就不显示内容。
class App extends React.Component {// ...render() {return (<div>{this.state.isLogin ? <>已登录</> : null}</div>)}}
列表渲染
1. map
如果箭头函数的代码块部分多于**一条**语句,就要使用大括号将它们括起来,并且使用
return语句返回。var sum = (num1, num2) => { return num1 + num2; }
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。
let getTempItem = id => ({ id: id, name: "Temp" });
class App extends React.Component {render() {const tabMenu = [{title: '常用联系人',tag: ''}...]return (<ul className="tab">{tabMenu.map((item, index) => (<li key={item.title}>{item.title}</li>))}</ul>);}}
一定要注意:key值一定要放在就近的数组上下文中。
2. for / forEach
import React from 'react';import './App.css';class App extends React.Component {render() {const tabMenus = [{title: '常用联系人',tag: ''}...]let tabMenuLi = []for(let i = 0; i < tabMenus.length; i++) {const item = tabMenu[i]tabMenuLi.push(<li key={item.title}>{item.title}</li>)}return (<ul className="tab">{ tabMenuLi }</ul>)}}export default App;
