-
受控组件 V.S. 非受控组件
<FInput value={x} onChange={fn}/> 受控组件:渲染表单的React组件 还控制着 用户输入表单时变化的操作,每次状态变化都调用onChange,这种组件叫受控组件。表单数据是由 React 组件来管理的。 <FInput defaultValue={x} ref={input}/> 非受控组件:React赋予组件一个初始值,但是不去控制后续的更新。表单数据将交由 DOM 节点来处理。
-
React
有哪些生命周期函数?分别有什么用?(Ajax
请求放在哪个阶段?)
初始化阶段:
componentWillMount()
组件即将被渲染到页面之前触发,此时可以进行开启定时器、向服务器发送请求等操作
render()
组件渲染
componentDidMount()
组件已经被渲染到页面中后触发:此时页面中有了真正的DOM
的元素,可以进行DOM
相关的操作
运行中阶段:
componentWillReceiveProps()
组件接收到属性时触发
shouldComponentUpdate()
当组件接收到新属性,或者组件的状态发生改变时触发。组件首次渲染时并不会触发
componentWillUpdate()
组件即将被更新时触发
render()
componentDidUpdate()
组件被更新完成后触发。页面中产生了新的DOM的元素,可以进行DOM操作
销毁阶段
componentWillUnmount()
组件被销毁时触发。- 在
componentDidMount()
中请求数据。
- 在
-
React
如何实现组件间通信?- 父子靠
props
传函数 - 爷孙可以穿两次
props
- 任意组件用
Redux
(也可以自己写一个eventBus
)
- 父子靠
-
shouldComponentUpdate
有什么用?- 用于在没有必要更新 UI 的时候返回
false
,以提高渲染性能。 - 但是不要滥用,这是
React
提供的一个紧急出口,必要的时候再使用,因为它的维护成本比较高,比如你新加了一个props
,但是又忘记在shouldComponentUpdate
写,就会引起bug。 - 如何优化?
- 用于在没有必要更新 UI 的时候返回
- 虚拟 DOM 是什么?
- 要点:虚拟 DOM 就是用来模拟 DOM 的一个对象,这个对象拥有一些重要属性,并且更新 UI 主要就是通过对比(DIFF)旧的虚拟 DOM 树 和新的虚拟 DOM 树的区别完成的。
- 参考:http://foio.github.io/virtual-dom/
- 什么是高阶组件?
- 文档原话——高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
- React-Redux 里 connect 就是一个高阶组件,比如
connect(mapState)(MyComponent)
接受组件 MyComponent,返回一个具有状态的新 MyComponent 组件。
- React diff 的原理是什么?
首先,React diff遵循三个策略:1. Web UI 中,DOM节点跨层级的移动操作特别少,可以忽略不计。2.拥有相同类的两个组件将生成相似的树形结构,拥有不同类的两个组件将生成不同的树形结构。3. 对于同一层级的一组子节点,可以通过唯一id来区分它们。基于以上三个策略,React分别对tree diff、component diff、element diff进行了算法优化。
-
tree diff
React对树的算法进行了优化,对树进行分层比较,两棵树只比较同一层级的节点。DOM节点跨层级的操作少到忽略不计,针对这一点,React通过updateDepth对Virtual DOM树进行层级控制,只会对相同颜色方块内的DOM节点进行比较,当发现节点不存在,则会删除该节点以及所有子节点,不会再进行进一步比较,所以只要对DOM树进行一次遍历,就能完成整个DOM树的比较。
-
component diff
如果是同一类型的组件,则按照原策略进行Virtual DOM Tree的比较。如果不是呢,则讲组件判断为dirty component,从而替换整个组件下的所有子节点。
对于同一类型的组件,React允许用户使用shouldComponentUpdate()
来判断该组件是否需要diff。
如下图,当component D 改变为 component G时,即使两个组件结构相似,一旦React判断两者为不同类型的组件,则不会进行比较,而是直接删除component D,重新创建component G以及所有子节点。
element diff
当节点处于同一层级时,React diff提供了三种节点操作
INSERT_MARKUP(插入):新的component类型不在老集合里,即是全新的节点,则需要对新节点执行插入操作。
MOVE_EXISTING(移动):新的component类型在老集合里,且element是可更新的类型,generateComponentChildren
已调用receiveComponent
,这种情况下prevChild=nextChild
,就需要执行移动操作,可以复用以前的DOM节点。
REMOVE_NODE(删除):老component类型在新集合里面也有,但对应的element不同则不能直接进行复用和更新,需要执行删除操作。老component类型在新集合里没有,也要执行删除操作。
开发者对同一层级的子节点,可以添加唯一索引进行区分,这样在diff时,涉及到只是位置变化的,可以只移动元素,避免删除创建等重复的操作。
- Redux 是什么?
-
Redux
是JavaScript
状态管理工具,提供可预测化的状态管理。 -
Action
:是把数据从应用传到Store
的有效载荷。它是Store
数据的唯一来源。改变State
的唯一办法,就是使用 Action。
-
import { ADD_TODO } from "../actionTypes";
export const addTodo = (payload:any) => {
return {
type: ADD_TODO,
payload
}
}
Reducers
: 指定了应用状态的变化如何响应 actions
并发送到store
的,记住 actions
只是描述了有事情发生了这一事实,并没有描述应用如何更新 state
。
Reducer
是一个函数,它接受 Action
和当前State
作为参数,返回一个新的 State
。
我们还可以将拆分后的Reducer
放到不同的文件中, 以保持其独立性并用于专门处理不同的数据域。然后使用combineReducers
将多个Reducer
合并成一个,输出成一个大的对象。
import { ADD_TODO } from "../actionTypes";
export default function (state = [],action:any) {
switch (action.type){
case ADD_TODO:
return [...state,action.payload]
default:
return state
}
}
import { ADD_TOMATO } from "../actionTypes";
export default function (state = [], action: any) {
switch(action.type) {
case ADD_TOMATO:
return [...state, action.payload]
default:
return state
}
}
//使用combineReducers
import { combineReducers } from "redux";
import todos from './todos'
import tomatoes from './tomatoes'
export default combineReducers({ todos, tomatoes });
Store
: 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store
。Redux
提供createStore
这个函数,接受reducers
,用来生成 Store
。
import { createStore } from "redux";
import rootReducer from "./reducers";
const store = createStore(rootReducer)
export default store
Store
有以下职责:
- 维持应用的
state
; - 提供
getState()
方法返回应用当前的 state 树。它与 store 的最后一个 reducer 返回值相同。 - 提供
dispatch(action)
方法分发action
。这是触发state
变化的惟一途径。 - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener)
返回的函数注销监听器。
connect
:该API连接React
组件与 Redux store
,连接操作不会改变原来的组件类。反而返回一个新的已与 Redux store 连接的组件类。connect可以接受参数,将参数注入到组件当中
const mapStateToProps = (state: { todos: any; }, ownProps: any) => ({//注入props
...ownProps
})
const mapDispatchToProps = {//注入属性
editTodo,
updateTodo
}
export default connect(mapStateToProps, mapDispatchToProps)(todoItem)
<Provider store>
使组件层级中的 connect()
方法都能够获得 Redux store
。正常情况下,你的根组件应该嵌套在 <Provider>
中才能使用 connect()
方法。
ReactDOM.render(
<Provider store={store}>
<MyRootComponent />
</Provider>,
rootEl
)
- connect 的原理是什么?
react-redux
库提供的一个 API,connect
的作用是让你把组件和store
连接起来,产生一个新的组件(connect 是高阶组件)。
1.首先connect
之所以会成功,是因为Provider
组件:
- 在原应用组件上包裹一层,使原来整个应用成为Provider
的子组件
- 接收Redux
的store
作为props
,通过context
对象传递给子孙组件上的connect
2.connect
做了什么:它真正连接Redux
和React
,它包在我们的容器组件的外一层,它接收上面Provider
提供的store
里面的state
和 dispatch
,传给一个构造函数,返回一个对象,以属性形式传给我们的容器组件。
3.主逻辑源码
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;
}
}
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