一、你了解hook吗
Hook 是react新特性,它是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的js函数。通过使用 Hook,我们可以把组件内一个状态相关的功能放在在一起,而不要把它们拆分到不同的生命周期函数里。
常用的hook:
1.useState 通过在函数组件里调用它来给组件添加一些内部 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。useState 唯一的参数就是 state的初始值。
2.useEffect 它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
二、用过ref吗?
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
/*
- 子组件调用父组件的方法: 将父组件的方法以函数属性的形式传递给子组件, 子组件就可以调用
- 父组件调用子组件的方法: 在父组件中通过ref得到子组件标签对象(也就是组件对象), 调用其方法
*/
/*
使用ref
- 创建ref容器: this.pw = React.createRef()
- 将ref容器交给需要获取的标签元素:
- 父组件通过ref容器读取标签元素: this.pw.current
*/
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建一个 ref 来存储 textInput 的 DOM 元素
this.textInput = React.createRef();
this.focusTextInput = this.focusTextInput.bind(this);
}
focusTextInput() {
// 直接使用原生 API 使 text 输入框获得焦点
// 注意:我们通过 "current" 来访问 DOM 节点,focus()是子组件的方法
this.textInput.current.focus();
}
render() {
// 告诉 React 我们想把 <input> ref 关联到
// 构造器里创建的 `textInput` 上
return (
<div>
<input
type="text"
ref={this.textInput} />
</div>
);
}
}
三、使用react与原生js相比的优势
1.react是声明式编程,原生js是命令式,要直接操作dom,react可以使我们只关注数据的变化
2.组件化:方便团队协同工作,不同组件耦合性低,互不影响,并且组件可以复用,并且便于日后维护
3.一次学习,随处编写,不仅能写web应用,还通过react native框架写手机应用。这个得益于虚拟dom,虚拟dom是一个js对象,抽象了原本的渲染过程,虚拟dom可以映射成浏览器dom,还可以映射成安卓系统,ios系统的控件树,从而实现了跨平台的能力。
四、state的理解和setState()之后会发生什么?
创建一个 class组件,并且继承于 React.Component。添加一个空的 render() 方法,每次组件更新时 render 方法都会被调用,在组件里添加一个 constructor构造函数,然后在该函数中为 this.state 赋初值,state是一个组件私有的,无论是父组件或是子组件都无法知道某个组件是有状态的还是无状态的,但组件可以选择把它的 state 作为 props 向下传递到它的子组件中。state不可以直接修改,而是应该使用 setState(),当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state,这里的合并是浅合并????,然后React将构建一个新的虚拟dom树,新的虚拟dom树和旧的虚拟dom树通过diff算法进行对比,在虚拟dom上找出变更,将需要变更的虚拟dom创建成真实dom,多个真实dom可以添加到createDocumentFragment()片段上,然后把这个片段更新到真实dom的相应位置。State 的更新可能是异步的,如果想获取更新后的数据可以在setState()第二个参数的位置传入一个回调函数,这个回调函数等价于componentDidUpdate,可以获取到更新后的state;也可以给 setState 传递一个函数,而不是一个对象,就可以确保每次的调用都是使用最新版的 state。
将虚拟的vnode变为真实dom
const dom = document.createElement( vnode.tag );
if( vnode.attrs ){
Object.keys( vnode.attrs ).forEach( key => {
const value = vnode.attrs[ key ];
setAttribute( dom, key, value ); // 设置属性
} );
}
vnode.children.forEach( child => render( child, dom ) );
更新真实dom
let el = document.querySelector("#app");
let fragment = document.createDocumentFragment();
let vDiv = document.createElement('div');
vDiv.textContent = 'init value';
fragment.appendChild(vDiv);
el.appendChild(fragment);
setState()的两种方式:
1)setState({}): 合并更新一次状态, 只调用一次render()更新界面 —状态更新和界面更新都合并了,这里第二次调用的this.state.counter和第一次相同。例如原本this.state.counter=0,执行两次后为1;
this.setState({
counter: this.state.counter + 1
});
this.setState({
counter: this.state.counter + 1
});
2)setState(fn): 更新多次状态, 但只调用一次render()更新界面 —状态更新没有合并, 但界面更新合并了。这里第二次调用的this.state.counter是第一次加1之后的值。例如原本this.state.counter=0,执行两次后为2;
this.setState((state, props) => ({
counter: state.counter + 1
}));
this.setState((state, props) => ({
counter: state.counter + 1
}));
为什么 React 不同步地更新 this.state?
如前面章节解释的那样,在开始重新渲染之前,React 会有意地进行“等待”,直到所有在组件的事件处理函数内调用的 setState() 完成之后。这样可以通过避免不必要的重新渲染来提升性能。
五、将事件处理函数声明为 class 中的方法
方法一:
this.handleClick = this.handleClick.bind(this);
…
解释:在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined。
方法二:
如果觉得绑定bind()很麻烦,可以使用 class fields语法,Create React App 默认启用此语法。
handleClick = () => {
console.log(‘this is:’, this);
}
…
方法三:
可以在回调函数中使用箭头函数
<button onClick={() => this.handleClick()}>
此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。