目的是为了防止 XSS 攻击。因为 Synbol 无法被序列化,所以 React 可以通过有没有 $$typeof 属性来断出当前的 element 对象是从数据库来的还是自己生成的。
// 服务端允许用户存储 JSON
let expectedTextButGotJSON = {
type: 'div',
props: {
dangerouslySetInnerHTML: {
__html: '/* 把你想的搁着 */'
},
},
// ...
};
let message = { text: expectedTextButGotJSON };
// React 0.13 中有风险
<p>
{message.text}
</p>
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;
}
}
中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过 滤,日志输出,异常报告等功能
常见的中间件:
<Link>
标签和<a>
标签有什么区别对比
<a>
,Link
组件避免了不必要的重渲染
在了解setState之前,我们先来简单了解下 React 一个包装结构: Transaction:
事务 (Transaction)
是 React 中的一个调用结构,用于包装一个方法,结构为: initialize - perform(method) - close。通过事务,可以统一管理一个方法的开始与结束;处于事务流中,表示进程正在执行一些操作
异步与同步: setState并不是单纯的异步或同步,这其实与调用时的环境相关:
注意事项:
为什么要用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};
Ajax请求应该写在组件创建期的第五个阶段,即 componentDidMount生命周期方法中。原因如下。
在创建期的其他阶段,组件尚未渲染完成。而在存在期的5个阶段,又不能确保生命周期方法一定会执行(如通过 shouldComponentUpdate方法优化更新等)。在销毀期,组件即将被销毁,请求数据变得无意义。因此在这些阶段发岀Ajax请求显然不是最好的选择。
在组件尚未挂载之前,Ajax请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount方法中,执行Ajax即可保证组件已经挂载,并且能够正常更新组件。
在代码中调用 setState 函数之后,React 会将传入的参数与之前的状态进行合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。在 React 得到元素树之后,React 会计算出新的树和老的树之间的差异,然后根据差异对界面进行最小化重新渲染。通过 diff 算法,React 能够精确制导哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
createElement
函数是 JSX 编译之后使用的创建React Element
的函数,而cloneElement
则是用于复制某个元素并传入新的Props
Keys
是React
用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识
key
在其同级元素中具有唯一性。在 React Diff
算法中React
会借助元素的 Key
值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key
值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key
的重要性HOC 和 Vue 中的 mixins 作用是一致的,并且在早期 React 也是使用 mixins 的方式。但是在使用 class 的方式创建组件以后,mixins 的方式就不能使用了,并且其实 mixins 也是存在一些问题的,比如:
state
并且在 mixin
中使用了,就这存在了一个依赖关系。万一下次别人要移除它,就得去 mixin
中查找依赖mixin
中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就是重写了,其实我一直觉得命名真的是一件麻烦事。。mixin
,但是一个 mixin
会被多个组件使用,可能会存在需求使得 mixin
修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本HOC 解决了这些问题,并且它们达成的效果也是一致的,同时也更加的政治正确(毕竟更加函数式了)。
从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶ <Link>
是react-router 里实现路由跳转的链接,一般配合<Route>
使用,react-router接管了其默认的链接跳转行为,区别于传统的页面跳转,<Link>
的“跳转”行为只会触发相匹配的<Route>
对应的页面内容更新,而不会刷新整个页面。
<Link>
做了3件事情:
<a>
标签就是普通的超链接了,用于从当前页面跳转到href指向的另一 个页面(非锚点情况)。a标签默认事件禁掉之后做了什么才实现了跳转?
let domArr = document.getElementsByTagName('a')
[...domArr].forEach(item=>{
item.addEventListener('click',function () {
location.href = this.href
})
})
React.createClass和extends Component的bai区别主要在于:
(1)语法区别
(2)propType 和 getDefaultProps
(3)状态的区别
(4)this区别
(5)Mixins
React mixins
的特性将不能被使用了。使用<Redirect>
组件实现路由的重定向:
<Switch>
<Redirect from='/users/:id' to='/users/profile/:id'/>
<Route path='/users/profile/:id' component={Profile}/>
</Switch>
当请求 /users/:id
被重定向去 '/users/profile/:id'
:
from: string
:需要匹配的将要被重定向路径。to: string
:重定向的 URL 字符串to: object
:重定向的 location 对象push: bool
:若为真,重定向操作将会把新地址加入到访问历史记录里面,并且无法回退到前面的页面。shouldComponentUpdate
进行判断可以用ref来获取某个子节点的实例,然后通过当前class组件实例的一些特定属性来直接获取子节点实例。ref有三种实现方法:
<p ref="info">span</p>
<p ref={ele => this.info = ele}></p>
state 更新流程: 这个过程当中涉及的函数:
注意:此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。应该考虑使用内置的 PureComponent 组件,而不是手动编写
shouldComponentUpdate()
props 更新流程: 相对于 state 更新,props 更新后唯一的区别是增加了对 componentWillReceiveProps 的调用。关于 componentWillReceiveProps,需要知道这些事情:
<div onClick={this.handleClick.bind(this)}>点我</div>
React并不是将click事件绑定到了div的真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。
除此之外,冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。 JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document
上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document
上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation
是无效的,而应该调用 event.preventDefault
。
实现合成事件的目的如下:
首先说说为什么要使用
Virturl DOM
,因为操作真实DOM
的耗费的性能代价太高,所以react
内部使用js
实现了一套dom结构,在每次操作在和真实dom之前,使用实现好的diff算法,对虚拟dom进行比较,递归找出有变化的dom节点,然后对其进行更新操作。为了实现虚拟DOM
,我们需要把每一种节点类型抽象成对象,每一种节点类型有自己的属性,也就是prop,每次进行diff
的时候,react
会先比较该节点类型,假如节点类型不一样,那么react
会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较prop
是否有更新,假如有prop
不一样,那么react
会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。