🐧React
# 🐧React
# React知识体系
写业务
- React基础
- React Hooks
- React Router
- 状态管理容器
- Redux
- MobX
- UI组件库
- Ant Design of React
性能优化
- React源码解析,相关原理
基础架构
- 脚手架配置
# React学习资源
# 入门
入门学习阶段,参考学习资源——
21/10-11
这里看出来是初学了 有些啰嗦 回头重读下文档 精简一下
# 进阶
复读官方文档,高级部分、API部分、Hooks部分都可以涉及+项目+阅读源码
可以学习一些hooks3.0库的源码
阅读经典书籍
- 《深入React技术栈》
# React基础知识
# HTML3
<!DOCTYPE html> <html> <head> <script src="../build/react.js"></script> <script src="../build/react-dom.js"></script> <script src="../build/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/babel"> // ** Our code goes here! ** </script> </body> </html>
<script>
标签的 type
属性为 text/babel
。
这是因为 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel"
。
上面代码一共用了三个库: react.js
、react-dom.js
和 Browser.js
,它们必须首先加载。其中
react.js
是 React 的核心库react-dom.js
是提供与 DOM 相关的功能Browser.js
的作用是将 JSX 语法转为 JavaScript 语法,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。
# ReactDOM.render()
ReactDOM.render
是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。
ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') );
上面代码将一个 h1
标题,插入 example
节点(查看 demo01
(opens new window)),运行结果如下。
# JSX
# JSX语法
HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法 (opens new window),它允许 HTML 与 JavaScript 的混写(查看 Demo02
(opens new window) )。
旧版本
var names = ['Alice', 'Emily', 'Kate']; ReactDOM.render( <div> { names.map(function (name) { return <div>Hello, {name}!</div> }) } </div>, document.getElementById('example') );
新版本
涉及了key的内容
需要注意!如果涉及到遍历列表的需求,key最好不要用index!
小结:如果不存在对数据的逆序添加、逆序删除,仅仅是为了渲染列表用于展示,那就可以使用index作为key
开发中如何选取key?
有唯一标识尽量还是用唯一标识!
ReactDOM.render( <div> { names.map(function (name, index) { return <div key={index}>Hello, {name}!</div> }) } </div>, document.getElementById('example') );
上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 <
开头),就用 HTML 规则解析;遇到代码块(以 {
开头),就用 JavaScript 规则解析。
# JSX中会展开数组
JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员(查看 demo03
(opens new window) )。
var arr = [ <h1>Hello world!</h1>, <h2>React is awesome</h2>, ]; ReactDOM.render( <div>{arr}</div>, document.getElementById('example') );
上面代码的arr
变量是一个数组,结果 JSX 会把它的所有成员,添加到模板。
# React面向组件编程
# “组件类”
React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。React.createClass
方法就用于生成一个组件类(查看 demo04
(opens new window))。
旧版本
var HelloMessage = React.createClass({ render: function() { return <h1>Hello {this.props.name}</h1>; } }); ReactDOM.render( <HelloMessage name="John" />, document.getElementById('example') );
新版本
class HelloMessage extends React.Component{ render(){ return <h1>Hello {this.props.name}</h1>; } }
组件“类”
上面代码中,变量 HelloMessage
就是一个组件类。模板插入 <HelloMessage />
时,会自动生成 HelloMessage
的一个实例(下文的"组件"都指组件类的实例)
“自动生成组件类的一个实例”这个概念好像之前没听见过!学习了!
# 组件类使用render方法输出组件
所有组件类都必须有自己的 render
方法,用于输出组件。
注意,组件类的第一个字母必须大写,否则会报错,比如HelloMessage
不能写成helloMessage
。
另外,组件类只能包含一个顶层标签,否则也会报错。
var HelloMessage = React.createClass({ render: function() { return <h1> Hello {this.props.name} </h1><p> some text </p>; } });
上面代码会报错,因为
HelloMessage
组件包含了两个顶层标签:h1
和p
。
# 组件实例从组件类的this.props
对象获取属性
组件的用法与原生的 HTML 标签完全一致,可以任意加入属性,比如 <HelloMessage name="John">
,就是 HelloMessage
组件加入一个 name
属性,值为 John
。组件的属性可以在组件类的 this.props
对象上获取,比如 name
属性就可以通过 this.props.name
读取。
# 组件属性名的注意点
添加组件属性,有一个地方需要注意,就是 class
属性需要写成 className
,for
属性需要写成 htmlFor
,这是因为 class
和 for
是 JavaScript 的保留字。
# Props
# this.props.children
-组件所有子节点
this.props
对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children
属性。它表示组件的所有子节点(查看 demo05
(opens new window))。
旧版本
var NotesList = React.createClass({ render: function() { return ( <ol> { React.Children.map(this.props.children, function (child) { return <li>{child}</li>; }) } </ol> ); } }); ReactDOM.render( <NotesList> <span>hello</span> <span>world</span> </NotesList>, document.body );
新版本
class NotesList extends React.Component{ render(){ return ( <ol> { React.Children.map(this.props.children, function(child){ return <li>{child}</li> }) } </ol> ) } }
上面代码的 NoteList
组件有两个 span
子节点,它们都可以通过 this.props.children
读取,运行结果如下。
# 用React.Children.map
遍历子节点
这里需要注意, this.props.children
的值有三种可能:
- 如果当前组件没有子节点,它就是
undefined
; - 如果有一个子节点,数据类型是
object
; - 如果有多个子节点,数据类型就是
array
。
所以,处理 this.props.children
的时候要小心。
React 提供一个工具方法 React.Children-中文文档
(opens new window) 来处理 this.props.children
。我们可以用 React.Children.map
来遍历子节点,而不用担心 this.props.children
的数据类型是 undefined
还是 object
。更多的 React.Children
的方法,请参考官方文档-英文原版 (opens new window)。
# PropTypes
-验证使用组件时提供的参数是否符合要求
老版本中阮大使用
const Com React.createClass
创建组件,与我之前学到的(更接近ES6规范的class写法)class Com extends React.Component
不太一样,且一些地方的用法也不同可以看看这篇文章 (opens new window)所阐述的这两者之间的区别
1、语法区别 2、propType 和 getDefaultProps 3、状态的区别 4、this区别 5、Mixins
组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。
组件类的PropTypes
属性,就是用来验证组件实例的属性是否符合要求(查看 demo06
(opens new window))。
老版本
通过
proTypes
对象和getDefaultProps()
方法来设置和获取props
.var MyTitle = React.createClass({ propTypes: { title:React.PropTypes.string.isRequired, }, render: function(){ return <h1>{this.props.title}</h1> } })
新版本
通过设置两个属性
propTypes
和defaultProps
class MyTitle extends React.Component{ // 改为属性 static propTypes = { title: PropType.string.isRequired, } render(){ return <h1> {this.props.title} </h1>; } }
上面的Mytitle
组件有一个title
属性。PropTypes
告诉 React,这个 title
属性是必须的,而且它的值必须是字符串。
举个反例来测试下
设置 title
属性的值是一个数值。
var data = 123; ReactDOM.render( <MyTitle title={data} />, document.body );
这样一来,title
属性就通不过验证了。控制台会显示一行错误信息。
Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.
更多的PropTypes
设置,可以查看官方文档 (opens new window)。
之前在fcc中学习到的 定义Props类型 (opens new window)是在组件类之外
上面学习到的直接在组件类里面定义也很是简单!
class ShoppingCart extends React.Component { constructor(porps){ super(props); } render(){ return <Items /> } } const Items = (porps) => { return <h1>Current Quantity of Items in Cart:{props.quantity}</h1> } // 这里定义 propTypes 属性 验证组件是否接收了正确类型的 props Items.propTypes = {quantity:PropTypes.number.isRequired};
# getDefaultProps
-设置组件属性的默认值
旧版本
var MyTitle = React.createClass({ getDefaultProps : function () { return { title : 'Hello World' }; }, render: function() { return <h1> {this.props.title} </h1>; } }); ReactDOM.render( <MyTitle />, document.body );
新版本
class MyTitle extends React.Component{ static defaultProps = { // as static property title: 'Hello World' }; render(){ return <h1>{this.props.title}</h1> } }
上面代码会输出"Hello World"。
# State
# 组件与用户的互动-this.state
React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI (查看 demo08
(opens new window) )。
旧版本
var LikeButton = React.createClass({ /*getInitialState: function() { return {liked: false}; },*/ handleClick: function(event) { this.setState({liked: !this.state.liked}); }, render: function() { var text = this.state.liked ? 'like' : 'haven\'t liked'; return ( <p onClick={this.handleClick}> You {text} this. Click to toggle. </p> ); } }); ReactDOM.render( <LikeButton />, document.getElementById('example') );
新版本
class LikeButton extends React.Component{ /*constructor(props){ super(props); this.state = { liked : false } this.handleClick = this.handleClick.bind(this);// 时刻记住要绑定这个方法 }*/ handleClick(event){ this.setState({ liked: !this.state.liked; }) } render(){ var text = this.state.liked ? 'like' : 'haven\'t liked'; return( <p onClick={this.handleClick}> You {text} this.Click to toggle </p> ) } }
上面代码是一个 LikeButton
组件,
它的
getInitialState
方法用于定义——
- 初始状态state(新版本中使用构造函数来定义state),也就是一个对象,这个对象可以通过
this.state
属性读取。
当用户点击组件,导致状态变化,this.setState
方法就修改状态值,每次修改以后,自动调用 this.render
方法,再次渲染组件。
# 区分props与state
由于 this.props
和 this.state
都用于描述组件的特性,可能会产生混淆。
一个简单的区分方法是:
this.props
表示那些一旦定义,就不再改变的特性- 而
this.state
是会随着用户互动而产生变化的特性。
官方文档 (opens new window)是这么说的:
props(“properties” 的缩写)和 state 都是普通的 JavaScript 对象。 它们都是用来保存信息的,这些信息可以控制组件的渲染输出,而它们的一个重要的不同点就是:
- props 是传递给组件的(类似于函数的形参)
- 而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)
官方推荐阅读材料
# Refs (opens new window)
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
在典型的 React 数据流中,props (opens new window) 是父组件与子组件交互的唯一方式。要修改一个子组件,你需要使用新的 props 来重新渲染它。
但是,在某些情况下,你需要在典型数据流之外强制修改子组件。被修改的子组件可能是一个 React 组件的实例,也可能是一个 DOM 元素。对于这两种情况,React 都提供了解决办法。
# 何时使用 Refs
下面是几个适合使用 refs 的情况:
- 管理焦点,文本选择或媒体播放。
- 触发强制动画。
- 集成第三方 DOM 库。
避免使用 refs 来做任何可以通过声明式实现来完成的事情。
举个例子,避免在 Dialog
组件里暴露 open()
和 close()
方法,最好传递 isOpen
属性。
# 勿过度使用 Refs
你可能首先会想到使用 refs 在你的 app 中“让事情发生”。如果是这种情况,请花一点时间,认真再考虑一下 state 属性应该被安排在哪个组件层中。通常你会想明白,让更高的组件层级拥有这个 state,是更恰当的。查看 状态提升 (opens new window) 以获取更多有关示例。
# 实例-创建 Refs
Refs 是使用 React.createRef()
创建的,并通过 ref
属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef(); }
render() {
return <div ref={this.myRef} />; }
}
# 访问 Refs
当 ref 被传递给 render
中的元素时,对该节点的引用可以在 ref 的 current
属性中被访问。
const node = this.myRef.current;
ref 的值根据节点的类型而有所不同:
- 当
ref
属性用于 HTML 元素时,构造函数中使用React.createRef()
创建的ref
接收底层 DOM 元素作为其current
属性。 - 当
ref
属性用于自定义 class 组件时,ref
对象接收组件的挂载实例作为其current
属性。 - 你不能在函数组件上使用
ref
属性,因为他们没有实例。
# 实例-将虚拟DOM插入文档,获取真实DOM
组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。
根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff (opens new window) ,它可以极大提高网页的性能表现。
但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 ref
属性(查看 demo07 (opens new window) )。
旧版本
var MyComponent = React.createClass({ handleClick: function(){ this.refs.myTextInput.focus(); } render: function() { return ( <div> <input type="text" ref="myTextInput" /> <input type="button" value="Focus the text input" onClick={this.handleClick} /> </div> ); } })
需要注意,本例中提到的字符型的ref属性已经过时了 (opens new window)!(主要是因为效率不高+存在一些问题 (opens new window))
不过用这个的程序员可能还挺多 因为比较简单省事儿~
严谨起见,建议使用下面新版本中的回调函数方式 (opens new window)!
使用了
React.createRef
(opens new window),创建一个能够通过ref属性附加到 React 元素的 ref (opens new window)。class MyComponent extends React.Component{ constructor(props){ super(props); this.myTextInput = React.createRef();// myTextInput就是创建的Refs this.handleClick = this.handleClick.bind(this);// 绑定点击事件 } handleClick(){ // 点击事件的回调函数,作用:在点击按钮之后对“render中的元素”进行聚焦 this.myTextInput.current.focus(); } render(){ return( <div> {/* 将ref传给render中的元素,对该节点(input输入框)的引用可以在ref的current属性中访问!见上面第9行的代码 */} <input type="text" ref={this.myTextInput} /> <input type="button" value="Focus te text input" onClick={this.handleClick} /> </div> ) } }
上面代码中,组件 MyComponent
的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref
属性,然后 this.refs.[refName]
就会返回这个真实的 DOM 节点。
需要注意的是,由于
this.refs.[refName]
属性获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。上面代码中,通过为组件指定
Click
事件的回调函数,确保了只有等到真实 DOM 发生Click
事件之后,才会读取this.refs.[refName]
属性。React 组件支持很多事件,除了
Click
事件以外,还有KeyDown
、Copy
、Scroll
等,完整的事件清单请查看官方文档 (opens new window)。
# 表单
# React中表单元素&DOM元素有些不同!
在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:
<form>
<label>
名字:
<input type="text" name="name" />
</label>
<input type="submit" value="提交" />
</form>
此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。
# 使用受控组件访问用户填写的表单数据
- 在 HTML 中,表单元素(如
<input>
、<textarea>
和<select>
)之类的表单元素通常自己维护 状态,并根据用户输入进行更新。- 而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用
setState()
(opens new window)来更新。把两者结合起来,使react得state成为“唯一数据源”;渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。
被React**以这种方式控制(用
setState()
更新表单属性值)**取值得表单输入元素就叫做——受控组件
用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props
读取(查看 demo9
(opens new window) )。
旧版本
没有构造函数看起来雀氏有点怪哈
var Input = React.createClass({ getInitialState: function() { return {value: 'Hello!'}; }, handleChange: function(event) { this.setState({value: event.target.value}); }, render: function () { var value = this.state.value; return ( <div> <input type="text" value={value} onChange={this.handleChange} /> <p>{value}</p> </div> ); } }); ReactDOM.render(<Input/>, document.body);
新版本
这里我稍微改了一下state中属性的变量名,原来的跟
event.target.value
中的value重叠了有点难受XDclass Input extends React.Component { constructor(props) { super(props) this.state = { slogan: 'Hi!This is bill~' } this.handleChange = this.handleChange.bind(this) } handleChange(event) { this.setState({ slogan: event.target.value }); } render() { var slogan = this.state.slogan; return ( <div> <input type="text" value={slogan} onChange={this.handleChange} /> <p>{slogan}</p> </div> ); } } ReactDOM.render(<Input/>, document.getElementById('example'));
我们通过
event.target.value
来读取用户输入的值,并用这个值更新组件state中的对应属性(value)
上面代码中,文本输入框的值,不能用 this.props.value
读取,而要定义一个 onChange
事件的回调函数,通过 event.target.value
读取用户输入的值。
textarea
元素、select
元素、radio
元素都属于这种情况,更多介绍请参考官方文档 (opens new window)。
- 小结
受控组件使用state来存数据,创建回调函数并使用setState({数据:event.target.value})
来更新数据,在表单中使用事件监听并调用回调函数。
# 受控组件的替代品-非受控组件
有时使用受控组件会很麻烦,因为你需要为数据变化的每种方式都编写事件处理函数,并通过一个React组件传递所有的输入state。
当你将之前的代码库转换为 React 或将 React 应用程序与非 React 库集成时,这可能会令人厌烦。在这些情况下,你可能希望使用非受控组件 (opens new window), 这是实现输入表单的另一种方式。
在大多数情况下,我们推荐使用 受控组件 (opens new window) 来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。
有木有很耳熟?访问DOM节点来获取表单数据?
要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,我们可以 使用 ref (opens new window) 来从 DOM 节点中获取表单数据。
# 组件生命周期
# 组件生命周期的简单理解
- 组件从创建到死亡它会经历一些特定的阶段。
- React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
- 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。
组件的生命周期 (opens new window)分成三个状态:
- Mounting:已插入真实 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真实 DOM
React 为每个状态都提供了处理函数,
will
函数在进入状态之前调用did
函数在进入状态之后调用unmount
函数在结束状态时调用,做一些收尾工作
三种状态共计五种处理函数(钩子函数 即为生命周期回调函数)。
componentWillMount()
×componentDidMount()
√componentWillUpdate(object nextProps, object nextState)
×componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()
√
此外,React 还提供两种特殊状态的处理函数。
componentWillReceiveProps(object nextProps)
:已加载组件收到新的参数时调用shouldComponentUpdate(object nextProps, object nextState)
:组件判断是否重新渲染时调用
这些方法的详细说明,可以参考官方文档 (opens new window)。下面是一个例子(查看 demo10
(opens new window) )。
旧版本
var Hello = React.createClass({ getInitialState: function () { return { opacity: 1.0 }; }, componentDidMount: function () { this.timer = setInterval(function () { var opacity = this.state.opacity; opacity -= .05; if (opacity < 0.1) { opacity = 1.0; } this.setState({ opacity: opacity }); }.bind(this), 100); }, render: function () { return ( <div style={{opacity: this.state.opacity}}> Hello {this.props.name} </div> ); } }); ReactDOM.render( <Hello name="world"/>, document.body );
新版本
class Hello extends React.Component{ constructor(props){ super(props); this.state = { opacity: 1 } } componentDidMount(){ // 在这个钩子函数中一般会进行一些初始化的事 this.timer = setInterval(function(){ var opacity = this.state.opacity; opacity -= 0.05; if(opacity < 0.1){ opacity = 1.0; } this.setState({ opacity: opacity }) }.bind(this), 100);// 每隔0.1秒 将透明度减少0.05,并使用setState更新opacity透明度 } render(){ return( <div style = {{opacity: this.state.opacity}}>Hello {this.props.name}!</div> ); } }
上面代码在hello
组件加载以后,通过 componentDidMount
方法设置一个定时器,每隔100毫秒,就重新设置组件的透明度,从而引发重新渲染。
另外,组件的style
属性的设置方式也值得注意,不能写成
style="opacity:{this.state.opacity};"
而要写成
style={{opacity: this.state.opacity}}
这是因为 React 组件样式是一个对象,所以第一重大括号表示这是 JavaScript 语法,第二重大括号表示样式对象。
# 重要的生命周期钩子&即将废弃的钩子
# 重要(常用的)
render
:初始化渲染或更新渲染调用componentDidMount
:用于在页面一上来就进行的一些操作,一般在这个钩子里做一些初始化的事。比如开启监听、开启定时器、 用来发送ajax请求componentWillUnmount
:做一些收尾工作, 如: 清理定时器
# 即将废弃
componentWillMount
componentWillReceiveProps
componentWillUpdate
# 生命周期流程图(旧)&生命周期三个阶段
【1】ReactDOM.render()
触发---初次渲染
- constructor()
componentWillMount()
生命周期方法会在版本 16.X 废弃在版本 17 移除- render()——常用(这肯定)
componentDidMount()
—— 常用- 用于在页面一上来就进行的一些操作,一般在这个钩子里做一些初始化的事 比如:
- 开启定时器
- 发送网络请求
- 订阅消息
- 【来自
freecodecamp
】某些时候,大多数 web 开发人员需要调用 API 接口来获取数据。 如果正在使用 React,知道在哪里执行这个动作是很重要的。所以React 的最佳实践是在生命周期方法componentDidMount()
中对服务器进行 API 调用或任何其它调用 (opens new window)。 将组件装载到DOM 后会调用此生命周期方法。- 此处对
setState()
的任何调用都将触发组件的重新渲染。 在此方法中调用 API 并用 API 返回的数据设置 state 时,一旦收到数据,它将自动触发更新。
- 此处对
- 用于在页面一上来就进行的一些操作,一般在这个钩子里做一些初始化的事 比如:
【2】由组件内部this.setSate()
或父组件从render
触发 —更新阶段
其实就是上面最后一点说的 “对setState
的任何调用都会触发组件的重新渲染(即为更新)”
shouldComponentUpdate()
componentWillUpdate()
- 即将废弃render()
componentDidUpdate()
【3】由ReactDOM.unmountComponentAtNode()
触发 — 卸载组件
componentWillUnmount()
——常用 一般在这个钩子中做一些收尾的事情 例如:- 关闭定时器
- 取消订阅消息
# 生命周期流程图(新)&新生命周期的三个阶段
【1】ReactDOM.render()
触发---初次渲染
- constructor()
componentWillMount()
生命周期方法会在版本 16.X 废弃在版本 17 移除- render()——常用(这肯定)
componentDidMount()
—— 常用 ,好比“人刚出生”
新增:
getDerivedStateFromProps
【2】由组件内部this.setSate()
或父组件从render
触发 —更新阶段
shouldComponentUpdate()
- 废弃componentWillUpdate()
render()
componentDidUpdate()
新增:
getDerivedStateFromProps
getSnapshotBeforeUpdate
【3】由ReactDOM.unmountComponentAtNode()
触发 — 卸载组件
componentWillUnmount()
——常用,好比“人的寿命走向终点”
# React Ajax
# 通过Ajax请求从服务器获取数据并传入组件
组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount
方法设置 Ajax 请求,等到请求成功,再用 this.setState
方法重新渲染 UI (查看 demo11
(opens new window) )。
旧版本
var UserGist = React.createClass({ getInitialState: function() { return { username: '', lastGistUrl: '' }; }, componentDidMount: function() { $.get(this.props.source, function(result) { var lastGist = result[0]; if (this.isMounted()) { this.setState({ username: lastGist.owner.login, lastGistUrl: lastGist.html_url }); } }.bind(this)); }, render: function() { return ( <div> {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>. </div> ); } }); ReactDOM.render( <UserGist source="https://api.github.com/users/octocat/gists" />, document.body );
新版本
class UserGist extends React.Component { constructor(props) { super(props) this.state = { username: '', lastGistUrl: '' } } componentDidMount(){ $.get(this.props.source, function(result){ var lastGist = result[0]; this.setState({ username: lastGist.owner.login, lastGistUrl: lastGist.html_url }); }.bind(this)); } render(){ return( <div> {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>. </div> ); } } ReactDOM.render( <UserGist source="https://api.github.com/users/octocat/gists" />, document.getElementById('example') );
上面代码使用 jQuery 完成 Ajax 请求,这是为了便于说明。React 本身没有任何依赖,完全可以不用jQuery,而使用其他库。
# 将promise对象传入组件
我们甚至可以把一个Promise对象传入组件,请看Demo12
(opens new window)。
ReactDOM.render( <RepoList promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')} />, document.body );
上面代码从Github
的API抓取数据,然后将Promise对象作为属性,传给RepoList
组件。
如果Promise对象正在抓取数据(pending状态),组件显示"正在加载";
如果Promise对象报错(rejected状态),组件显示报错信息;
如果Promise对象抓取数据成功(fulfilled状态),组件显示获取的数据。
class RepoList extends React.Component {
constructor(props) {
super(props)
this.state = {
loading: true,
error: null,
data: null
};
}
componentDidMount() {
this.props.promise.then(
value => this.setState({loading: false, data: value}),
error => this.setState({loading: false, error: error}));
}
render() {
// 请求中
if (this.state.loading) {
return <span>Loading...</span>;
}
// 请求失败
else if (this.state.error !== null) {
return <span>Error: {this.state.error.message}</span>;
}
// q
else {
var repos = this.state.data.items;
var repoList = repos.map(function (repo, index) {
return (
<li key={index}><a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description}</li>
);
});
return (
<main>
<h1>Most Popular JavaScript Projects in Github</h1>
<ol>{repoList}</ol>
</main>
);
}
}
}
- 请求中
- 请求失败
- 请求成功
# React原理
# 虚拟DOM与DOM Diffing算法
比对两个虚拟DOM时会有三种操作:删除、替换、更新
对比两棵树时 React会首先比较两棵树的根节点,不同类型的根节点元素会有不同的形态。
- 删除:newVnode不存在
- 替换:vnode和newVnode类型不同或key不同
- 更新:有相同类型和key,但vnode和newVnode不同
首次调用render进行页面渲染 将容器节点中的所有DOM元素都替换掉,后续进行更新渲染时则会使用React的diffing算法进行高效更新~
# diff算法高效在哪里?
这种自上而下的React Node节点更新按理来说是很笨重的 使用diff算法结合fiber架构 使更新变为分片的,不会一直占据着JS引擎 导致阻塞渲染线程、卡顿
# React页面挂载过程
- 组件被称为"状态机", 通过更新组件的state(状态)来更新对应的页面显示(重新渲染组件 - 每次调用render 就会渲染组件)
# 状态管理-MobX,redux
十分钟入门 MobX & React (opens new window)
搭配官方文档&API文档&实际项目中的代码食用最佳~
# 类组件
# 类组件获取DOM
# 大神绘图 React 生命周期 (opens new window)
componentWillMounted - 渲染前的生命周期——对应哪个钩子? 22/3/25 字节二面提到的内容
# React Hooks
# 函数式组件获取DOM
# useMemo & useCallback
# useEffect
# 轻松学会 React 钩子:以 useEffect() 为例 - 阮一峰的网络日志 (opens new window)
# useState与useRef的区别
# useRef (opens new window)
const renderCount = useRef(0)
将变量存在renderCount.current
属性中- 特点:改变这个属性不会引发组件重新渲染
- 在input中使用
const inputRef = useRef
+<input ref={inputRef}>
inputRef.current
可以获取到对应的input元素
# 两个钩子的区别
setState函数会更新state。它接收一个新的state值并将组件的一次重新渲染加入任务队列。
ref对象内容发生变化时,useRef不会通知我们
- 也就是说变更
.current
属性不会引发组件重新渲染 - 当我们想存储一个值并不希望更新值的时候触发页面的渲染时,可以使用useRef
- 也就是说变更