react 用于构建用户界面的 JavaScript 库 。
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MV* 框架,都不满意,就决定自己写一套,用来架设 Instagram 照片墙 的网站(17年 用户量7亿+)。做出来以后,发现这套东西很好用,就在2013年5月开源了。
[英文文档](https://reactjs.org/)
[中文文档](https://doc.react-china.org/)
[github地址](https://github.com/facebook/react)
声明式:React 可以非常轻松地创建用户交互界面。为你应用的每一个状态设计简洁的视图,在数据改变时 React 也可以高效地更新渲染界面。
组件化:创建好拥有各自状态的组件,再由组件构成更加复杂的界面。
一次学习,随处编写:无论你现在正在使用什么技术栈,你都可以随时引入 React 开发新特性。React 也可以用作开发原生应用的框架 React Native.
使用组件化
开发方式,符合现代Web开发的趋势
技术成熟,社区完善,配件齐全,适用于大型Web项目(生态系统健全)
由Facebook专门的团队维护,技术支持可靠
ReactNative - Learn once, write anywhere: Build mobile apps with React
使用方式简单,性能非常高,支持服务端渲染 SSR
cdn的方式
本地的方式
注意,如果在浏览器端使用babel,必须指定type='text/babel'
准备html的结构
把元素渲染到页面
// 把元素渲染到页面中
// 参数1:需要渲染的react对象
// 参数2:指定渲染到页面中的容器
ReactDOM.render(
element,
document.getElementById('app')
)
[api地址](https://react.docschina.org/docs/react-api.html#createelement)
/*
作用:根据指定的参数,创建react对象
参数1:指定创建虚拟DOM的类型
类型:string 或者 react组件
1. 任意字符串类型的标签名称,比如:'div' / 'span'
2 react组件类型,比如:
参数2:指定元素自身的属性
类型:对象或者null
参数3:当前元素的子元素
类型:string 获取react对象
返回值:react对象
*/
React.createElement(
type,
[props],
[...children]
)
[api地址](https://react.docschina.org/docs/react-dom.html#render)
/*
功能:渲染一个React元素,添加到位于提供的container里的DOM元素中
参数1::指定要渲染的react对象
参数2:指定渲染到页面中的容器(DOM对象)
参数3:回调函数
*/
ReactDOM.render(
element,
document.getElementById('app')
)
总结:使用createElement创建react对象非常的麻烦,推荐您使用JSX
- 苹果
- 香蕉
- 橘子
// 创建一个react对象
let li1 = React.createElement('li', null, '苹果')
let li2 = React.createElement('li', null, '香蕉')
let li3 = React.createElement('li', null, '橘子')
let ul = React.createElement('ul', {id: 'list'}, li1, li2, li3)
// 把react对象渲染到页面
ReactDOM.render(ul, document.getElementById('app'))
// 通过jsx语法创建react对象
const element = 你好啊
// 渲染react对象
ReactDOM.render(
element,
document.getElementById('app')
)
jsx: 一种 JavaScript 的语法扩展。 在react中推荐使用react来描述用户界面(创建react对象)
通过jsx创建react对象,实质内部还是调用createElement方法,只不过jsx语法更加的简洁
jsx虽然看起来像模板,但是jsx是一个js对象,并不是字符串,也不是DOM对象
在jsx中可以使用表达式, 只需要使用{}
包裹起来即可
为了代码可读性,书写jsx的时候需要保证代码的缩进,并且给jsx的代码使用()
包裹起来
jsx中的元素只能有一个根元素,并且标签必须闭合。
因为 JSX 的特性更接近 JavaScript 而不是 HTML ,所以jsx的属性是js的属性,比如class变成了className
React 中最值得称道的部分莫过于 Virtual DOM 与 diff 的完美结合,特别是其高效的 diff 算法,让用户可以无需顾忌性能问题而”任性自由”的刷新页面 。
虚拟DOM(Virtual DOM):虚拟DOM就是使用javascript的对象来描述了DOM结构 。
为什么要有虚拟DOM?
因为操作真实DOM的代价是非常昂贵的,随便一个DOM对象,都有很多很多的属性
DOM中大部分的属性都不是我们需要关注的
虚拟DOM:
使用js对象来描述一个DOM结构,在react框架中,描述一个DOM的对象,使用的一个虚拟DOM
页面的更新可以先全部反映在js对象上,操作内存中的js对象的速度显然要快多了,不需要重绘与回流
等更新完后,再将最终的js对象映射成真实的DOM,交由浏览器去绘制。
react中通过diff算法来决定如何更新视图中的内容
[官方文档-diff]([diff算法 - 中文文档](https://www.reactjscn.com/docs/reconciliation.html))
如果两棵树的根元素类型不同,React会销毁旧树,创建新树:
// 旧树
张三
// 新树
张三
对于类型相同的React DOM 元素,如果仅仅是属性不同,只更新不同的属性 :
// 旧
// 新
只更新:className 属性
// 旧
// 新
只更新:color属性
添加节点的情况 :
// 旧
- first
- second
// 新
- first
- second
- third
// 在虚拟DOM中添加 third
// 效率不高
// 旧
- a
- b
// 新
- c
- a
- b
// React将改变每一个子节点, 不会保证a b 不变化
带key的情况:
// 旧
- a
- b
// 新
- c
- a
- b
// react会根据唯一的key来移动元素
// 在遍历数据时,推荐在使用 key 属性
使用虚拟DOM(JavaScript)对象结构表示 DOM 树的结构,根据虚拟DOM渲染出真正的DOM树
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异(diff算法)
把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
[虚拟DOM介绍](https://www.jianshu.com/p/616999666920)
[如何实现一个 Virtual DOM 算法](https://github.com/livoras/blog/issues/13)
函数组件:
function Hello(props) {
return (
大家好
{props.name} ---- {props.age}
)
}
ReactDOM.render(
,
document.getElementById('app')
)
// 定义组件 函数组件
// 1. 定义的组件名必须首字母大写,为了和html标签区分开
// 2. 必须返回一个jsx
// 组件传值:
// 组件中怎么接受到传递过来的值, 通过props参数来接收即可
// props是只读的, 在组件接收到的内容不允许修改
// 函数组件: 有一个缺点, 没有状态 函数组件自己没有数据
// 函数组件中的数据都是 父组件传递过来的数据, 组件无法修改
类组件:
class Hello extends React.Component {
// 在类组件中想要获取传递过来的值,也是通过props来获取
constructor(props) {
// 表示把props添加给了当前组件, 将来在任意的函数中,都可以通过this.props来访问到
super(props)
}
render() {
return (
大家好 --{this.props.name} ----{this.props.age}
)
}
}
// 怎么给类组件传值
ReactDOM.render(
,
document.getElementById('app')
)
计时器案例(使用函数组件和类组件完成)
function Clock (props) {
return (
北京时间
当前时间:{props.date.toLocaleString()}
)
}
setInterval(() => {
// 如何渲染的Clock组件
ReactDOM.render(
,
document.getElementById('app')
)
}, 1000)
// 定义一个clock组件
class Clock extends React.Component {
constructor(props) {
super(props)
this.state = {
date: new Date(),
name: 'zs'
}
}
render() {
return (
北京时间
当前时间:{this.state.date.toLocaleString()}
大家好,我的名字是{this.state.name}
)
}
// DOM结构渲染好了, 开启一个定时器,动态的修改时间
componentDidMount() {
this.timeId = setInterval( () => {
// 修改状态, 在react中,修改状态不能直接通过this.state.xxx来修改
// 必须通过this.setState()来进行修改
// this.state.date = new Date()
// this.state.name = '李四'
// // 同步
// this.setState(this.state)
// 原因:react框架和小程序 不是双向数据绑定的
// vue和angular都是双向数据绑定
this.setState({
date: new Date(),
name: 'ls'
})
}, 1000)
}
// 关闭定时器
componentWillUnmount () {
clearInterval(this.timeId)
}
}
ReactDOM.render(
,
document.getElementById('app')
)
https://www.jianshu.com/p/514fe21b9914
旧版生命周期如果要开启async rendering,在render函数之前的所有函数,都有可能被执行多次。
getDerivedStateFromProps:
getDerivedStateFromProps无论是Mounting还是Updating,也无论是因为什么引起的Updating,全部都会被调用。
getSnapshotBeforeUpdate:
getSnapshotBeforeUpdate()被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
官网例子:
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
//我们是否要添加新的 items 到列表?
// 捕捉滚动位置,以便我们可以稍后调整滚动.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
//如果我们有snapshot值, 我们已经添加了 新的items.
// 调整滚动以至于这些新的items 不会将旧items推出视图。
// (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
{/* ...contents... */}
);
}
}