react组件分为类(class)组件和函数(function)组件。
class 组件
是通过继承模版类(Component、PureComponent)的方式开发新组件的,继承是 class 本身的特性,它支持设置 state,会在 state 改变后重新渲染,可以重写一些父类的方法,这些方法会在 React 组件渲染的不同阶段调用,叫做生命周期函数。
function 组件
不能做继承,因为 function 本来就没这个特性,并且函数式组件也没有生命周期,所以react提供了一些 api 供函数来弥补函数组件的缺陷,这些 api 会在内部的一个数据结构上挂载一些函数和值,并执行相应的逻辑,通过这种方式实现了 state
和类似 class 组件的生命周期函数
的功能,这种 api 就叫做 hooks
。
其实我们已经使用过一些hooks了,例如useState
来实现响应式的数据功能,使用useEffect
实现类组件才拥有的组件生命周期,但是都没有详细地研究其参数和使用。
state保存着当前组件的状态
,当我们改变state,页面ui就会自动更新,及实现了响应式
。
类组件中使用state:
函数组件需要借助useState
:
用法:
const [stateName, setStateName] = React.useState(stateValue);
以下代码实现了一秒后页面上的文字变化:
function MyComponent() {
const [msg, setMsg] = React.useState("Hello React!");
setTimeout(() => setMsg("Hello Hooks!"), 1000);
return <p>{msg}</p>;
}
ReactDOM.render(<MyComponent />, document.getElementById("app"));
React hooks也提供了 api ,用于弥补函数组件没有生命周期的缺陷。
用法:
useEffect(()=>{
return destory
},dep)
useEffect 第一个参数 是一个函数, 返回的 destory , destory 作为下一次callback执行之前调用,用于清除上一次 回调函数产生的副作用。
第二个参数作为依赖项,是一个数组,可以有多个依赖项,依赖项改变,执行上一次回调函数返回的 destory ,和执行新的 effect 第一个参数。
对于 useEffect 执行, React 处理逻辑是采用异步调用
,对于每一个 effect 的 callback, React 会向 setTimeout回调函数一样,放入任务队列
,等到主线程任务完成,DOM 更新,js 执行完成,视图绘制完毕,才执行。所以 effect 回调函数不会阻塞浏览器绘制视图。
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>useEffecttitle>
<script src="https://cdn.staticfile.org/react/16.8.0/umd/react.development.js">script>
<script src="https://cdn.staticfile.org/react-dom/16.8.0/umd/react-dom.development.js">script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js">script>
head>
<body>
<div id="app">div>
<script type="text/babel">
const Clock = (props) => {
const [n, setN] = React.useState(0);
function addNum() {
setN(n + 1);
}
React.useEffect(() => {
console.log(n);
});
return (
<div>
<p>现在的n是 {n} .</p>
<button onClick={addNum}>n+1</button>
</div>
);
};
ReactDOM.render(<Clock />, document.getElementById("app"));
script>
body>
html>
当useEffect没有第二个参数时,组件的初始化和更新都会执行。
useEffect(() => {
//doSomething
}, [])
useEffect的第二个参数为一个空数组,初始化调用一次之后不再执行,相当于componentDidMount
。
useEffect(() => {
//doSomething
}, [])
useEffect返回一个函数,这个函数会在组件卸载
时执行。
useEffect(() => {
return () => {
...
};
}, []);
它返回的是一个 memoized 值,仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。我们可以拿 vue 里面的 计算属性computed
和它做一个对比,都是返回基于依赖重新计算的值。
const cacheSomething = useMemo(create,deps)
① create:第一个参数为一个函数,函数的返回值作为缓存值。
② deps: 第二个参数为一个数组
,存放当前 useMemo 的依赖项,在函数组件下一次执行的时候,会对比 deps 依赖项里面的状态,是否有改变,如果有改变重新执行 create ,得到新的缓存值。
③ acheSomething:返回值,执行 create 的返回值。如果 deps 中有依赖项改变,返回的重新执行 create 产生的值,否则取上一次缓存值。
function MyComponent() {
const [n, setN] = React.useState(0);
const [t, setT] = React.useState(0);
const getT = () => {
console.log("调用getT获取T!");
return t;
};
return (
<div>
<p>n: {n}</p>
<p>t: {getT}</p>
<button onClick={() => setN(n + 1)}>n + 1</button>
<br />
<button onClick={() => setT(t + 1)}>t + 1</button>
</div>
);
}
ReactDOM.render(<MyComponent />, document.getElementById("app"));
此时无论改变n还是t,都会导致getT函数被调用,但是事实上,在n改变时,并不需要重新调用一遍getT函数。
我们使用useMemo来实现仅t改变,getT才重新运行。
function MyComponent() {
const [n, setN] = React.useState(0);
const [t, setT] = React.useState(0);
const getT = React.useMemo(() => {
console.log("调用getT获取T!");
return t;
}, [t]);
return (
<div>
<p>n: {n}</p>
<p>t: {getT}</p>
<button onClick={() => setN(n + 1)}>n + 1</button>
<br />
<button onClick={() => setT(t + 1)}>t + 1</button>
</div>
);
注意,此时getT不在是一个函数,而是useMemo回调函数返回的值,所以在页面上直接使用getT即可。
useMemo 和 useCallback 接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值。
区别在于 useMemo 返回的是函数运行的结果
,useCallback 返回的是函数
,这个回调函数是经过处理后的也就是说父组件传递一个函数给子组件的时候。
由于是无状态组件每一次都会重新生成新的 props 函数,这样就使得每一次传递给子组件的函数都发生了变化,这时候就会触发子组件的更新,这些更新是没有必要的,此时我们就可以通过 usecallback 来处理此函数,然后作为 props 传递给子组件。
我们依然利用useMemo的例子:
可以看到,此时getT是一个函数,并不是一个值,这也是useMemo和useCallback的区别,即返回结果不同。
可以使用 useContext ,来获取父级组件传递过来的 context 值(上下文
),这个当前值就是最近的父级组件 Provider 设置的 value 值,useContext 参数一般是由 createContext 方式创建的 ,也可以父级上下文 context 传递的 ( 参数为 context )。
创建context:
const context = createContext();
使用context:
const contextValue = useContext(context)
const Context = React.createContext();
const ChildComponent01 = () => {
const c = React.useContext(Context);
return (
<div>
<p>/* 用useContext方式 */</p>
My name is {c.name}, I'm {c.age}.
</div>
);
};
const ChildComponent02 = () => {
return (
<Context.Consumer>
{(value) => (
<div>
<p>/* 用Context.Consumer 方式 */</p>
My name is {value.name}, I'm {value.age}.
</div>
)}
</Context.Consumer>
);
};
const MyComponent = () => {
return (
<div>
<Context.Provider value={{ name: "yancy", age: 20 }}>
<ChildComponent01 />
<ChildComponent02 />
</Context.Provider>
</div>
);
}
ReactDOM.render(<MyComponent />, document.getElementById("app"));
使用useContext可以避免使用props进行传参造成数据传递十分繁琐和困难的问题。
在 React 数据流中,props 是父组件和子组件交互的唯一方式。要修改一个子组件,必须使用新的 props 去重新渲染它。而 refs 提供了另一种方式,允许我们在 React 典型数据流之外,去操作 DOM 元素
和类组件的实例
。
useRef 返回一个对象,返回的ref对象在组件的整个生命周期保持不变。
最常用的ref是两种对象
用法1: 引入DOM(或者组件,组件必须是类组件)元素
用饭2: 保存一个数据,这个数据在组件的整个生命周期中可以保存
基于组件的框架(react、vue)是不推荐直接操作dom元素的,例如使用document.getElementById(“”)来获取元素,react提供了useRef来使我们能够获取绑定的dom元素。
const MyComponent = () => {
const titleRef = React.useRef();
const inputRef = React.useRef();
function changeDOM() {
titleRef.current.innerHTML = "useRef";
inputRef.current.focus();
}
return (
<div>
<h2 ref={titleRef}> Hello World </h2>
<input ref={inputRef} type="text" />
<button onClick={(e) => changeDOM()}>修改DOM</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("app"));
还可以利用useRef配合useEffect来获取上一次的state:
const MyComponent = () => {
const [count, setCount] = React.useState(0);
const numRef = React.useRef(count);
React.useEffect(() => {
numRef.current = count;
}, [count]);
return (
<div>
<h2>count 上一次的值: {numRef.current}</h2>
<h2>count 这一次的值: {count}</h2>
<button onClick={(e) => setCount(count + 1)}>count + 1</button>
</div>
);
};
ReactDOM.render(<MyComponent />, document.getElementById("app"));
在 react 的 class组件写法中,随处可见各种各样的 .bind(this)。(甚至官方文档里也有专门的章节描述了“为什么绑定是必要的?”这一问题)
而在函数组件中通过使用hooks,可以完美地代替类组件,并且几乎不用关心this的指向问题。
Vue3.2的组合式api也引入了hooks,可以看到hooks是前端框架发展的趋势,是一个组件的灵魂
所在。