// 父组件
class Team extends PureComponent {
constructor() {
super()
this.state = {
name: 'oyo'
}
}
render() {
return (
<div>Team onwer is:
<People
name={this.state.name}
onClick={name => {
this.setState({ name })
}}
>
</People>
</div>
);
}
};
// 子组件
class People extends PureComponent {
render() {
return (
<span
onClick={() => {() => this.props.onClick('hello')}
>
{this.props.name}
</span>
)
}
});
调用setState之后发生了什么?
在代码中调用setState函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个UI界面。在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
扩展1
: setState的第一个参数除了对象,还能传什么? ---函数,参数为当前state
setState(state => {
return {
num: state.num + 1
}
})
扩展2
: setState的第二个参数有什么用?---回调
this.setState(
{ username: 'tylermcginnis33' },
() => console.log('setState has finished and the component has re-rendered.')
)
类似于vue中的this.$nextTick(),该函数会在setState函数调用完成并且组件重渲染后被调用
扩展3
: setState的时候如果两次state值没有发生变化,一定不会造成调用render吗? ---会
import React from 'react'
class Test extends React.Component{
constructor(props) {
super(props);
this.state = {
Number:1//设state中Number值为1
}
}
//这里调用了setState但是并没有改变setState中的值
handleClick = () => {
const preNumber = this.state.Number
this.setState({
Number:this.state.Number
})
}
render(){
//当render函数被调用时,打印当前的Number
console.log(this.state.Number)
return(
{this.state.Number}
)
}
}
export default Test
Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。我们可以为元素添加ref属性然后在回调函数中接受该元素在 DOM 树中的句柄. 我们可以使用ref的句柄来调用自组件的方法,甚至去setState, 但并不推荐这么去操作
扩展
: 能否说一下有在工作中具体使用的案例
class CustomForm extends Component {
handleSubmit = () => {
console.log("Input Value: ", this.input.value)
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
ref={(input) => this.input = input} />
<button type='submit'>Submit</button>
</form>
)
}
}
扩展2
: 使用ref去获取input值(利用DOM存放表单数据)后操作的我们称为非受控组件Uncontrolled Component,那么其与受控组件Controlled Component,直接使用state去操作input值有什么区别?
class ControlledForm extends Component {
state = {
username: ''
}
updateUsername = (e) => {
this.setState({
username: e.target.value,
})
}
handleSubmit = () => {}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
value={this.state.username}
onChange={this.updateUsername} />
<button type='submit'>Submit</button>
</form>
)
}
}
实际开发中我们并不提倡使用非受控组件,因为实际情况下我们需要更多的考虑表单验证、选择性的开启或者关闭按钮点击、强制输入格式等功能支持,而此时我们将数据托管到 React 中有助于我们更好地以声明式的方式完成这些功能。引入 React 或者其他 MVVM 框架最初的原因就是为了将我们从繁重的直接操作 DOM 中解放出来。
扩展三
: ref的三种使用方式
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.myRef = React.createRef()
}
componentDidMount() {
console.log(this.myRef.current)
}
render() {
return <div ref={this.myRef} />
}
}
扩展四
: 有没有理解过React.forwardRef?
React V16.3中,react推出forwardRef
再类似HOC高阶组件中, 我们如果需要把ref绑定至真正需要调用的子组件而不是HOC组件上时, 我们需要借助React.forwardRef去传递ref的引用
function HOC(Wrapper) {
class AddModal extends Component {
constructor(props) {
super(props)
this.state = {
name: '',
}
}
render() {
return <Wrapper {...this.props} ref={this.props.forwardedRef} data={this.state} />
}
}
return React.forwardRef((props, ref) => <AddModal {...props} forwardedRef={ref} />)
}
export default HOC
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。 提升优化react中的diff算法,避免不必要的节点摧毁与重建
render () {
return (
<ul>
{this.state.todoItems.map(({task, uid}) => {
return <li key={uid}>{task}</li>
})}
</ul>
)
}
在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。
扩展一
: 如果不加key值或者key值相同的情况可能会造成什么问题?
如下为react在源码中对key的比较,如果不同则会直接更新
// 用来判定两个element需不需要更新
// 这里的key是我们createElement的时候可以选择性的传入的。
// 用来标识这个element,当发现key不同时,我们就可以直接重新渲染,不需要去更新了。
var _shouldUpdateReactComponent = function (prevElement, nextElement) {
if (prevElement != null && nextElement != null) {
var prevType = typeof prevElement;
var nextType = typeof nextElement;
if (prevType === 'string' || prevType === 'number') {
return nextType === 'string' || nextType === 'number';
} else {
return
nextType === 'object' &&
prevElement.type === nextElement.type &&
prevElement.key === nextElement.key;
}
}
return false;
}
扩展二
: 循环的时候利用index去作为key值好不好?
key做为DOM节点标识,如果是前后两次arr分别为[1,2,3,4]和[5,6,7,8]和前后两次arr分别为[1,2,3,4]和[4,3,2,1]的情况,很明显前者可以认为是DOM改变了,后者可以认为是DOM节点的位移操作,那么对于第一种情况来说index作为key和没有key值无区别,但是第二种情况用index作为key值效果没有比用数据本身作为key值好,这里大家可以按照以上方式打印去看一下.所以结论是如果你的数据能确保唯一性,就用数据本身作为key值吧
jquery中的事件会被绑定在原生节点本身,而在react中会被统一绑定到document去代理
扩展
: 知道react中事件大致的注册以及触发的原理吗
注册时react会首先判断该组件上props是否是event事件,若是则绑定到document上,回调函数是dispatchEvent,将绑定了事件的react组件实例的rooNodeId(虚拟dom的唯一标识)取出来,作为key值,对应的回调函数作为value值存为一个对象
触发时事件冒泡传递到document的时候,会触发dispatchEvent的执行,根据目标实例的递归向上寻找目标实例的父元素和祖先元素,存到数组path中,然后遍历path,获取rooNodeId作为key值将之前存事件的对象value值全部取出,最后挨个执行回调
//对于新的属性,需要写到dom节点上
for (propKey in nextProps) {
//对于事件监听的属性我们需要特殊处理
if (/^on[A-Za-z]/.test(propKey)) {
var eventType = propKey.replace('on', '');
//以前如果已经有,说明有了监听,需要先去掉
lastProps[propKey] &&
$(document).undelegate(
'[data-reactid="' + this._rootNodeID + '"]',
eventType,
lastProps[propKey]);
//针对当前的节点添加事件代理,以_rootNodeID为命名空间
$(document).delegate(
'[data-reactid="' + this._rootNodeID + '"]',
eventType + '.' + this._rootNodeID,
nextProps[propKey]);
continue;
}
if (propKey == 'children') continue;
//添加新的属性,或者是更新老的同名属性
$('[data-reactid="' + this._rootNodeID + '"]').prop(propKey, nextProps[propKey])
}
用作上下文的的数据储存, react中数据以props传输,但在组件嵌套过深的情况下, pros需要逐层传递,相当的麻烦.
在新版本的react中, 使用React.createContext进行创建context对象.其会返回Provider(提供数据的父组件)以及Consumer(消费数据的子组件)两个对象进行使用,react-redux目前就是以这样的方式进行数据传递
// 创建context对象
import React from 'react'
import ReactDOM from 'react-dom'
const MyContext = React.createContext({
name: '凤嘉',
})
// 父组件使用Provider
class App extends React.Component {
render() {
return (
<MyContext.Provider store={{ name: '凤嘉' }}>
<Header />
</MyContext.Provider>
)
}
}
// 其中任意子组件消费store
class Title extends React.Component {
render() {
return (
<MyContext.Consumer>
{store => (
<h1>
{store.children}
</h1>
)}
</MyContext.Consumer>
)
}
}