React v16.8
发布了 Hooks
,其主要是解决跨组件、组件复用的状态管理问题。react
在 class
中组件的状态封装在对象中,而后经过单向数据流来组织组件间的状态交互。这种模式下,跨组件的状态管理变得很是困难,复用的组件也会由于要兼容不一样的组件变得产生不少反作用,若是对组件再次拆分,也会形成冗余代码增多,和组件过多带来的问题。git
后来有了 Redux
之类的状态管理库,来统一管理组件状态。可是这种分层依然会让代码变得很复杂,须要更多的嵌套、状态和方法,写代码时也常在几个文件之间不停切换。hooks
就是为了解决以上这些问题。github
文章不对 hooks
作太多详细介绍,建议阅读此文前,先到官网作大概的了解。此文基于上一篇文章《实现ssr服务端渲染》的代码,进行 hooks
改造。代码已经提交到仓库的 hooks
分支中,仓库连接 github.com/zimv/react-…。编程
在了解 hooks
的过程当中,慢慢的感受到了面向对象和函数式编程的区别。api
在 class
模式中状态和属性方法等被封装在组件内,组件之间是相互以完整对象个体作交互,状态的修改须要在对象内部的 setState 中处理。数组
而 hooks
模式中,一切皆函数,也就是 hooks
,能够被拆分红不少小单元再进行组合,修改状态的是一个 set
方法,此方法能够在任何其余的 hooks
中出现和调用。 class 更属于面向对象编程,而 hooks
更属于函数式编程。bash
React
也并不会移除 class
,而是引入 hooks
使开发者能根据场景作更好的选择。它们依旧会在将来保持应有的迭代。异步
使用 hooks
以后,本来的生命周期概念就会有所变化了。好比咱们定义一个 hooks
组件 Index, 当组件运行时,Index
函数的调用就是一次 render
,那么咱们第一次 render
至关于原来的 willMount
,而 useEffect
会在第一次 render
之后执行。官网文档也说过你能够把 useEffect
Hooks
视做 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的结合。 state
也被 useState
替代,useState
传入初始值并返回变量和修改变量的 set
方法。async
在咱们服务端渲染的时候,上篇文章说过生命周期只会执行到 willMount
后的第一次 render
。 那在咱们 hooks
模式下,服务端渲染会执行 Index hooks
第一次 render
,而 useEffect
不会被执行。函数式编程
function Index(props){
console.log('render');
const [desc, setDesc] = useState("惹不起");
useEffect(() => {
console.log('effect')
})
return (<div>{desc}</div>)
}复制代码
若是使用了 useEffect
,组件钩子每次 render
之后,useEffect
会被执行。 useEffect
第一个入参是须要调用的方法,方法能够返回一个方法,返回的方法会在非首次执行此 useEffect
以前调用,也会在组件卸载时调用。
第二个参数是传入一个数组,是用来限制 useEffect
执行次数的,若是不传入此参数,useEffect
会在每次 render
时执行。若是传入第二个数组参数,在非首次执行 useEffect
时,数组中的变量较上一次 render
发生了变化,才会再次触发 useEffect
执行。
看以下代码,当页面首次 render
,useEffect
执行异步数据获取,当数据获取成功,setList
设置值之后(相似 setState
会触发 render
),会再次执行 render
,而 useEffect
还会再次执行,数据请求结束之后,setList
又会致使 render
,所以陷入死循环。
const [list, setList] = useState([]);
useEffect(() => {
API.getData().then(data=>{
if (data) {
setList(data.list);
}
});
});复制代码
因此须要使用第二个参数,限制执行次数,咱们传入一个 1
,就能够实现仅执行一次 useEffect
。固然也能够经过传入一个 useState
变量。
const [list, setList] = useState([]);
useEffect(() => {
API.getData().then(data=>{
if (data) {
setList(data.list);
}
});
}, [1]);复制代码
在本来的 SSR 仓库的前提下,仅针对组件部分,进行 hooks
改造。首先回顾 getInitialProps
在 class
模式下,是在 class
写一个 static
静态方法,以下:
export default class Index extends Base {
static async getInitialProps() {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
}
}复制代码
在 hooks
中,class
变成了普通函数,之前的继承变得没有必要也没法适应需求,所以 getInitialProps
直接写在函数的属性中,方法自己返回的数据格式依然不变,返回一个对象。以下:
function Index(props) {
}
Index.getInitialProps = async () => {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
};复制代码
包括定义的网页 title
,以前也是使用 static
,如今咱们也 Index.title = 'index'
这样定义。
hooks
规范要求以下,援引中文 React
文档:
在 class
模式下,咱们继承了 Base
,Base
会定义 constructor
和 componentWillMount
来处理 state
和 props
,能够帮助咱们解决服务端渲染和客户端渲染下初始化状态数据的赋值和获取,所以咱们才能够统一一套代码在客户端和服务端中运行(如须要,查看上篇文章了解详情)。
class
模式下的继承 Base
属于面向对象编程模式,而 hooks
模式下,因为须要在函数内使用 useState
来定义状态,而且返回方法来设置状态,这样看起来更偏向函数式编程,在这种场景下,继承变得不适应。所以须要对 Base
进行改造,在 Base
编写 hooks
,在页面组件 hooks
中使用。
在 class
模式下,咱们使用继承 Base
来处理 state
和 props
,因为 Base
已经封装了 constructor
和 componentWillMount
处理 state
和 props
,所以咱们只须要定义好静态 state
和 getInitialProps
,组件便会自动处理相关逻辑,大体使用代码以下
export default class Index extends Base {
static state = {
desc: "Hello world~"
};
static async getInitialProps() {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
}
}复制代码
在 hooks
模式下不同,由于摒弃了继承,须要用 Base
自定义 hooks
,而后在页面组件中使用。Base
中的 getProps
和 requestInitialData
钩子调用时,须要传入当前 Index
组件的部分对象,而后在 Base hooks
中返回变量初始值值或者调用 set
修改当前 hooks
中的状态值,大体使用以下:
import { getProps, requestInitialData } from "../base";
function Index(props) {
const [desc, setDesc] = useState("Hello world~");
//getProps获取props中的ssrData,重构和服务端渲染时props有值,第三个参数为默认值
const [data, setData] = useState(getProps(props, "data", ""));
//在单页面路由页面跳转,渲染组件时,requestInitialData调用getInitialProps
requestInitialData(props, Index, { data: setData });
return (<div>{data}</div>)
}
Index.getInitialProps = async () => {
let data;
const res = await request.get("/api/getData");
if (!res.errCode) data = res.data;
return {
data
};
};
export default Index;复制代码
如此封装之后,咱们依然保证了一套代码能在服务端和客户端运行,requestInitialData
方法第三个传入参数,是一个对象,传入了须要被修改的状态的 set
方法,最终 getInitialProps
返回数据后,会和传入的对象对比,属性名一致便会调用 set
方法进行状态修改,requestInitialData
是一个 useEffect hook
,代码以下
export function requestInitialData(props, component, setFunctions) {
useEffect(() => {
//客户端运行时
if (typeof window != "undefined") {
//非同构时,而且getInitialProps存在
if (!props.ssrData && component.getInitialProps) {
component.getInitialProps().then(data => {
if (data) {
//遍历结果,执行set赋值
for (let key in setFunctions) {
for (let dataKey in data) {
if (key == dataKey) {
setFunctions[key](data[dataKey]);
break;
}
}
}
}
});
}
}
},[1]);
}复制代码
至此,针对我以前的 SSR 代码,就完成了 hooks
的改造。React hooks
的改造很是平滑,class
和 hooks
混用也不会形成什么问题,若是须要在旧的项目中使用 hooks
或者对原有的 class
进行改造,彻底能够慢慢的一部分一部分迭代。固然 React Hooks
还有 useContext
useReducer
等,不妨如今就去试试 Hooks ?
关联文章:《 实现ssr服务端渲染》
关联仓库: github.com/zimv/react-…