1.React如何实现数据更新?React的 Diff算法?
react通过虚拟dom的 Diff算法,在状态发生变化的时候创造新的虚拟节点和以前的进行对比,让变化的部分进行渲染。整个过程没有对dom进行获取和操作,只有一个渲染的过程,所以react是一个ui框架。
react采用setState来控制视图的更新,setState会自动调用render函数,触发视图的重新渲染,如果仅仅只是state数据的变化而没有调用setState,并不会触发更新
2. 列表渲染的key有什么用?
精准定位更新的列表位置
列表通常具有相同的结构,在对列表节点进行删除,插入,排序的时候,单个节点的整体操作远比一个个对比一个个替换要好得多,所以在创建列表的时候需要设置key值,这样react才能分清谁是谁。
3.React组件的生命周期
组件在初始化时会触发5个钩子函数:
(1) getDefaultProps()
设置默认的props,也可以用dufaultProps设置组件的默认属性。
(2) getInitialState()
在使用es6的class语法时是没有这个钩子函数的,可以直接在constructor中定义this.state。此时可以访问this.props。
(3) componentWillMount()
组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,此时可以修改state。
(4) render()
react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
(5) componentDidMount()
组件渲染之后调用,可以通过this.getDOMNode()获取和操作dom节点,只调用一次。
在更新时也会触发5个钩子函数:
(6) componentWillReceivePorps(nextProps)
组件初始化时不调用,组件接受新的props时调用。
(7) shouldComponentUpdate(nextProps, nextState)
react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候。不过调用this.forceUpdate会跳过此步骤。
(8) componentWillUpdate(nextProps, nextState)
组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state
(9) render()
react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。
(10) componentDidUpdate()
组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点。
还有一个卸载钩子函数
(11) componentWillUnmount()
组件将要卸载时调用,一些事件监听和定时器需要在此时清除
4.React-Router路由
Router就是React的一个组件,它并不会被渲染,只是一个创建内部路由规则的配置对象,根据匹配的路由地址展现相应的组件。
Route可以向绑定的组件传递7个属性:children,history,location,params,route,routeParams,routes,每个属性都包涵路由的相关的信息。比较常用的有children(以路由的包涵关系为区分的组件),location(包括地址,参数,地址切换方式,key值,hash值)。
react-router阻止了a标签的默认行为并用pushState进行hash值的转变。切换页面的过程是在点击Link标签或者后退前进按钮时,会先发生url地址的转变,Router监听到地址的改变根据Route的path属性匹配到对应的组件,将state值改成对应的组件并调用setState触发render函数重新渲染dom。
react配合webpack进行按需加载的方法:Route的component改为getComponent,组件用require.ensure的方式获取,并在webpack中配置chunkFilename。
const chooseProducts = (location, cb) => {
require.ensure([], require => {
cb(null, require('../Component/chooseProducts').default)
},'chooseProducts')
}
const saleRecord = (location, cb) => {
require.ensure([], require => {
cb(null, require('../Component/saleRecord').default)
},'saleRecord')
}
const RouteConfig = (
<Router history={history}>
<Route path="/" component={Roots}>
<IndexRoute component={index} />//首页
<Route path="index" component={index} />
<Route path="saleRecord" getComponent={saleRecord} />//销售记录
<Redirect from='*' to='/' />
</Route>
</Router>
);
5. React组件之间的通信
react推崇的是单向数据流,自上而下进行数据的传递,但是由下而上或者不在一条数据流上的组件之间的通信就会变的复杂。解决通信问题的方法很多,如果只是父子级关系,父级可以将一个回调函数当作属性传递给子级,子级可以直接调用函数从而和父级通信。
组件层级嵌套到比较深,可以使用上下文getChildContext来传递信息,这样在不需要将函数一层层往下传,任何一层的子级都可以通过this.context直接访问。
兄弟关系的组件之间无法直接通信,它们只能利用同一层的上级作为中转站。而如果兄弟组件都是最高层的组件,为了能够让它们进行通信,必须在它们外层再套一层组件,这个外层的组件起着保存数据,传递信息的作用,这其实就是redux所做的事情。
组件之间的信息还可以通过全局事件来传递。不同页面可以通过参数传递数据,下个页面可以用location.param来获取。
其实react本身很简单,难的在于如何优雅高效的实现组件之间数据的交流。
6.Redux
Redux的作用相当于在顶层组件之上又加了一个组件,作用是进行逻辑运算、储存数据和实现组件尤其是顶层组件的通信。如果组件之间的交流不多,逻辑不复杂,只是单纯的进行视图的渲染,这时候用回调,context就行,没必要用redux,用了反而影响开发速度。
react-redux提供了connect和Provider两个好基友,它们一个将组件与redux关联起来,一个将store传给组件。组件通过dispatch发出action,store根据action的type属性调用对应的reducer并传入state和这个action,reducer对state进行处理并返回一个新的state放入store,connect监听到store发生变化,调用setState更新组件,此时组件的props也就跟着变化。
React-Redux流程简述:
(1)Provider组件接受redux的store作为props,然后通过context往下传。
(2)connect函数收到Provider传出的store,然后接受三个参数mapStateToProps,mapDispatchToProps和组件,并将state和actionCreator以props传入组件,这时组件就可以调用actionCreator函数来触发reducer函数返回新的state,connect监听到state变化调用setState更新组件并将新的state传入组件。
connect可以写的非常简洁,mapStateToProps,mapDispatchToProps只不过是传入的回调函数,connect函数在必要的时候会调用它们,名字不是固定的,甚至可以不写名字。
connect(state => state, action)(Component);
export default connect(state => ({
formData: state.formData,
proData: state.proData,
}), {
saveFormData,
clearData,
})(Home);