Keys
是React
用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识
key
在其同级元素中具有唯一性。在 React Diff
算法中React
会借助元素的 Key
值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key
值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key
的重要性----------@----------
该函数会在
setState
函数调用完成并且组件开始重渲染的时候被调用,我们可以用该函数来监听渲染是否完成:
this.setState(
{
username: 'tylermcginnis33' },
() => console.log('setState has finished and the component has re-rendered.')
)
this.setState((prevState, props) => {
return {
streak: prevState.streak + props.count
}
})
----------@----------
Refs
是 React
提供给我们的安全访问 DOM
元素或者某个组件实例的句柄ref
属性然后在回调函数中接受该元素在 DOM
树中的句柄,该值会作为回调函数的第一个参数返回----------@----------
我们应当将AJAX 请求放到
componentDidMount
函数中执行,主要原因有下
React
下一代调和算法 Fiber
会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount
的触发次数。对于 componentWillMount
这个生命周期函数的调用次数会变得不确定,React
可能会多次频繁调用 componentWillMount
。如果我们将 AJAX
请求放到 componentWillMount
函数中,那么显而易见其会被触发多次,自然也就不是好的选择。AJAX
请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState
函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount
函数中进行 AJAX
请求则能有效避免这个问题----------@----------
shouldComponentUpdate
允许我们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新
----------@----------
通常情况下我们会使用
Webpack
的DefinePlugin
方法来将NODE_ENV
变量值设置为production
。编译版本中React
会忽略propType
验证以及其他的告警信息,同时还会降低代码库的大小,React
使用了Uglify
插件来移除生产环境下不必要的注释等信息
----------@----------
为了解决跨浏览器兼容性问题,
React
会将浏览器原生事件(Browser Native Event
)封装为合成事件(SyntheticEvent
)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有意思的是,React
并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样React
在更新DOM
的时候就不需要考虑如何去处理附着在DOM
上的事件监听器,最终达到优化性能的目的
----------@----------
createElement
函数是 JSX 编译之后使用的创建React Element
的函数,而cloneElement
则是用于复制某个元素并传入新的Props
----------@----------
中间件提供第三方插件的模式,自定义拦截
action
->reducer
的过程。变为action
->middlewares
->reducer
。这种机制可以让我们改变数据流,实现如异步action
,action
过滤,日志输出,异常报告等功能
redux-logger
:提供日志输出redux-thunk
:处理异步操作redux-promise
:处理异步操作,actionCreator
的返回值是promise
----------@----------
flux
中直接从store
取。render
,可能会有效率影响,或者需要写复杂的shouldComponentUpdate
进行判断。----------@----------
React-Redux
提供connect
方法联系起来----------@----------
初始化阶段
getDefaultProps
:获取实例的默认属性getInitialState
:获取每个实例的初始化状态componentWillMount
:组件即将被装载、渲染到页面上render
:组件在这里生成虚拟的DOM
节点componentDidMount
:组件真正在被装载之后运行中状态
componentWillReceiveProps
:组件将要接收到属性的时候调用shouldComponentUpdate
:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render
调用,后面的函数不会被继续执行了)componentWillUpdate
:组件即将更新不能修改属性和状态render
:组件重新描绘componentDidUpdate
:组件已经更新销毁阶段
componentWillUnmount
:组件即将销毁----------@----------
在新版本中,React 官方对生命周期有了新的 变动建议:
getDerivedStateFromProps
替换componentWillMount;
getSnapshotBeforeUpdate
替换componentWillUpdate;
componentWillReceiveProps
;其实该变动的原因,正是由于上述提到的
Fiber
。首先,从上面我们知道 React 可以分成reconciliation
与commit
两个阶段,对应的生命周期如下:
reconciliation
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
commit
componentDidMount
componentDidUpdate
componentWillUnmount
在
Fiber
中,reconciliation
阶段进行了任务分割,涉及到 暂停 和 重启,因此可能会导致reconciliation
中的生命周期函数在一次更新渲染循环中被 多次调用 的情况,产生一些意外错误
新版的建议生命周期如下:
class Component extends React.Component {
// 替换 `componentWillReceiveProps` ,
// 初始化和 update 时被调用
// 静态函数,无法使用 this
static getDerivedStateFromProps(nextProps, prevState) {
}
// 判断是否需要更新组件
// 可以用于组件性能优化
shouldComponentUpdate(nextProps, nextState) {
}
// 组件被挂载后触发
componentDidMount() {
}
// 替换 componentWillUpdate
// 可以在更新之前获取最新 dom 数据
getSnapshotBeforeUpdate() {
}
// 组件更新后调用
componentDidUpdate() {
}
// 组件即将销毁
componentWillUnmount() {
}
// 组件已销毁
componentDidUnMount() {
}
}
使用建议:
constructor
初始化 state
;componentDidMount
中进行事件监听,并在componentWillUnmount
中解绑事件;componentDidMount
中进行数据的请求,而不是在componentWillMount
;props
更新 state
时,使用getDerivedStateFromProps(nextProps, prevState)
;
public static getDerivedStateFromProps(nextProps, prevState) {
// 当新 props 中的 data 发生变化时,同步更新到 state 上
if (nextProps.data !== prevState.data) {
return {
data: nextProps.data
}
} else {
return null1
}
}
可以在componentDidUpdate监听 props 或者 state 的变化,例如:
componentDidUpdate(prevProps) {
// 当 id 发生变化时,重新获取数据
if (this.props.id !== prevProps.id) {
this.fetchData(this.props.id);
}
}
----------@----------
shouldComponentUpdate
这个方法用来判断是否需要调用render方法重新描绘dom。因为dom的描绘非常消耗性能,如果我们能在shouldComponentUpdate方
法中能够写出更优化的dom diff
算法,可以极大的提高性能
----------@----------
虚拟
dom
相当于在js
和真实dom
中间加了一个缓存,利用dom diff
算法避免了没有必要的dom
操作,从而提高性能
具体实现步骤如下
JavaScript
对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM
树,插到文档当中DOM
树上,视图就更新虚拟DOM一定会提高性能吗?
很多人认为虚拟DOM一定会提高性能,一定会更快,其实这个说法有点片面,因为虚拟DOM虽然会减少DOM操作,但也无法避免DOM操作
----------@----------
key
属性,方便比较。React
只会匹配相同 class
的 component
(这里面的class
指的是组件的名字)component
的 setState
方法的时候, React
将其标记为 - dirty
.到每一个事件循环结束, React
检查所有标记 dirty
的 component
重新绘制.shouldComponentUpdate
提高diff
的性能----------@----------
shouldComponentUpdate
来避免不必要的dom操作production
版本的react.js
key
来帮助React
识别列表中所有子组件的最小变化----------@----------
Flux
的最大特点,就是数据的"单向流动"。
View
View
发出用户的 Action
Dispatcher
收到Action
,要求 Store
进行相应的更新Store
更新后,发出一个"change"
事件View
收到"change"
事件后,更新页面----------@----------
1. JSX做表达式判断时候,需要强转为boolean类型
如果不使用
!!b
进行强转数据类型,会在页面里面输出0
。
render() {
const b = 0;
return
{
!!b && 这是一段文本
}
}
2. 尽量不要在 componentWillReviceProps
里使用 setState,如果一定要使用,那么需要判断结束条件,不然会出现无限重渲染,导致页面崩溃
3. 给组件添加ref时候,尽量不要使用匿名函数,因为当组件更新的时候,匿名函数会被当做新的prop处理,让ref属性接受到新函数的时候,react内部会先清空ref,也就是会以null为回调参数先执行一次ref这个props,然后在以该组件的实例执行一次ref,所以用匿名函数做ref的时候,有的时候去ref赋值后的属性会取到null
4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入
----------@----------
class Demo {
render() {
return
}
}
你觉得你这样设置点击事件会有什么问题吗?
由于
onClick
使用的是匿名函数,所有每次重渲染的时候,会把该onClick
当做一个新的prop
来处理,会将内部缓存的onClick
事件进行重新赋值,所以相对直接使用函数来说,可能有一点的性能下降
修改
class Demo {
onClick = (e) => {
alert('我点击了按钮')
}
render() {
return <button onClick={
this.onClick}>
按钮
</button>
}
----------@----------
首先说说为什么要使用
Virturl DOM
,因为操作真实DOM
的耗费的性能代价太高,所以react
内部使用js
实现了一套dom结构,在每次操作在和真实dom之前,使用实现好的diff算法,对虚拟dom进行比较,递归找出有变化的dom节点,然后对其进行更新操作。为了实现虚拟DOM
,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是prop,每次进行diff
的时候,react
会先比较该节点类型,假如节点类型不一样,那么react
会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较prop
是否有更新,假如有prop
不一样,那么react
会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点
----------@----------
通常我们输出节点的时候都是map一个数组然后返回一个
ReactNode
,为了方便react
内部进行优化,我们必须给每一个reactNode
添加key
,这个key prop
在设计值处不是给开发者用的,而是给react用的,大概的作用就是给每一个reactNode
添加一个身份标识,方便react进行识别,在重渲染过程中,如果key一样,若组件属性有所变化,则react
只更新组件对应的属性;没有变化则不更新,如果key不一样,则react先销毁该组件,然后重新创建该组件
----------@----------
----------@----------
Model
改变之后(可能是调用了setState
),触发了virtual dom
的更新,再用diff
算法来把virtual DOM
比较real DOM
,看看是哪个dom
节点更新了,再渲染real dom
----------@----------
标签和
标签有什么区别----------@----------
connect
之所以会成功,是因为Provider
组件:Provider
的子组件 接收Redux
的store
作为props
,通过context
对象传递给子孙组件上的connect
connect
做了些什么。它真正连接Redux
和React
,它包在我们的容器组件的外一层,它接收上面Provider
提供的store
里面的state
和dispatch
,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件
connect
是一个高阶函数,首先传入mapStateToProps
、mapDispatchToProps
,然后返回一个生产Component
的函数(wrapWithConnect
),然后再将真正的Component
作为参数传入wrapWithConnect
,这样就生产出一个经过包裹的Connect
组件,该组件具有如下特点
props.store
获取祖先Component
的store props
包括stateProps
、dispatchProps
、parentProps
,合并在一起得到nextState
,作为props
传给真正的Component componentDidMount
时,添加事件this.store.subscribe(this.handleChange)
,实现页面交互shouldComponentUpdate
时判断是否有避免进行渲染,提升页面性能,并得到nextState
componentWillUnmount
时移除注册的事件this.handleChange
由于
connect
的源码过长,我们只看主要逻辑
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {
}) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
constructor(props, context) {
// 从祖先Component处获得store
this.store = props.store || context.store
this.stateProps = computeStateProps(this.store, props)
this.dispatchProps = computeDispatchProps(this.store, props)
this.state = {
storeState: null }
// 对stateProps、dispatchProps、parentProps进行合并
this.updateState()
}
shouldComponentUpdate(nextProps, nextState) {
// 进行判断,当数据发生改变时,Component重新渲染
if (propsChanged || mapStateProducedChange || dispatchPropsChanged) {
this.updateState(nextProps)
return true
}
}
componentDidMount() {
// 改变Component的state
this.store.subscribe(() = {
this.setState({
storeState: this.store.getState()
})
})
}
render() {
// 生成包裹组件Connect
return (
<WrappedComponent {
...this.nextState} />
)
}
}
Connect.contextTypes = {
store: storeShape
}
return Connect;
}
}
----------@----------
为什么要用redux
在
React
中,数据在组件中是单向流动的,数据从一个方向父组件流向子组件(通过props
),所以,两个非父子组件之间通信就相对麻烦,redux
的出现就是为了解决state
里面的数据问题
Redux设计理念
Redux
是将整个应用状态存储到一个地方上称为store
,里面保存着一个状态树store tree
,组件可以派发(dispatch
)行为(action
)给store
,而不是直接通知其他组件,组件内部通过订阅store
中的状态state
来刷新自己的视图
Redux三大原则
整个应用的state都被存储到一个状态树里面,并且这个状态树,只存在于唯一的store中
state
是只读的,唯一改变state
的方法就是触发action
,action
是一个用于描述以发生时间的普通对象
使用纯函数来执行修改,为了描述
action
如何改变state
的,你需要编写reducers
Redux源码
let createStore = (reducer) => {
let state;
//获取状态对象
//存放所有的监听函数
let listeners = [];
let getState = () => state;
//提供一个方法供外部调用派发action
let dispath = (action) => {
//调用管理员reducer得到新的state
state = reducer(state, action);
//执行所有的监听函数
listeners.forEach((l) => l())
}
//订阅状态变化事件,当状态改变发生之后执行监听函数
let subscribe = (listener) => {
listeners.push(listener);
}
dispath();
return {
getState,
dispath,
subscribe
}
}
let combineReducers=(renducers)=>{
//传入一个renducers管理组,返回的是一个renducer
return function(state={
},action={
}){
let newState={
};
for(var attr in renducers){
newState[attr]=renducers[attr](state[attr],action)
}
return newState;
}
}
export {
createStore,combineReducers};
----------@----------
PureComponent
和Component
完全相同,但是在shouldComponentUpdate
实现中,PureComponent
使用了props
和state
的浅比较。主要作用是用来提高某些特定场景的性能
----------@----------
React 中通常使用 类定义 或者 函数定义 创建组件:
在类定义中,我们可以使用到许多 React 特性,例如 state、 各种组件生命周期钩子等,但是在函数定义中,我们却无能为力,因此 React 16.8 版本推出了一个新功能 (React Hooks),通过它,可以更好的在函数定义组件中使用 React 特性。
好处:
跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
类定义更为复杂
不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
时刻需要关注this的指向问题;
代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;