入门级操作
State
最简单的实现,我们在 Loading 组件内部声明一个状态,通过代码逻辑判断 loading 效果的展示。
export default class extends Component {
...
render() {
return this.state.loading ? :
finish
;
}
}
Props
随着业务的发展,这个 Loading 组件用到的地方会非常多,上面这个代码耦合了很多逻辑,为了让这个组件能够很好的复用,那我们抽离出组件的业务逻辑,将内部状态进行提升,那这个组件就是一个能被复用的 UI 组件。
export default function(props) {
return props.loading ? :
finish
;
}
完整演示
注:上面两段代码你可能会想,为什么 Func
和 Class
都能实现一个组件,他们有什么差别吗?
其实你在开发时不容易感觉到差别,但 React 本身是进行了很多差别处理,如果是 Class 类,React 会用 new
关键字实例化,然后调用该实例的 render
方法,如果是 Func 函数,React 会直接调用它。
Refs
如果你是一个 jQuery 转型 React 的开发,会很自然的想到,我找到 Loading 组件的节点,控制他的显示与隐藏,当然这也是可以的,React 提供 Refs 方便你访问 DOM 节点 或 React 元素。
export default class extends Component {
componentDidMount() {
fetch().then(() => {
this.el.changeLoading(false);
});
}
render() {
return (
{ this.el = el; }} />
);
}
}
通用逻辑抽离
当你的应用做到一定的复杂度,不同的页面都会有 loading 效果,你肯定不希望每个页面都重复的书写一样的逻辑,这样会导致你的代码重复且混乱。
React 中有两个比较常见的解决方案 HOC
和 Render Props
,其实这两个这两个概念都是不依赖 React 的。
让我们暂时忘掉 React,下面我对 HOC
和 Render Props
写两个例子,你会发现组件复用是如此简单。
HOC
HOC 其实就是一种装饰器模式,它接受一个组件作为参数,然后返回相同的组件,这样就可以额外增加一些功能。
const func = () => {
console.log("func");
};
const wrap = func => {
console.log("wrap");
return func;
};
// wrap 逻辑已被复用
wrap(func)();
Render Props
Render Props 就是我们给一个函数传递一个回调函数做为参数,该回调函数就能利用外面函数的执行结果做为参数,执行任何操作。
const func = param => {
console.log("func");
};
const wrap = (param, func) => {
console.log("wrap");
func(param);
};
// wrap 逻辑已被复用
wrap("", func);
完整演示
相同点:
- 两者都能很好的帮助我们
重用组件逻辑
; - 和回调函数类似,当嵌套层数很多时,会造成
回调地狱
。
不同点:
- HOC 和 父组件有相同属性名属性传递过来,会造成属性丢失;
- Render Props 你只需要实例化一个中间类,而 HOC 你每次调用的地方都需要额外实例化一个中间类。
总的来说,在需要复用组件逻辑的时候,我个人更倾向于 Render Props 的方式。
复杂状态管理
当你的应用越来越大,组件之间交互越来越复杂,那整个页面的数据逻辑将变得难以管理,这时候为了方便管理应用的状态,你可以选择一些状态管理工具,例如 Redux、Flux、dva 等。
Redux
我不太想谈这些数据流框架,因为他们的概念 action
、store
、dispatch
太过于生涩难懂。
现代前端框架 React 和 Vue 其实都是一个套路,通过数据渲染试图,然后视图上操作反过来更新数据,重新渲染视图,刷新页面。
数据叫做 store
,动作叫做 ation
,触发行为叫 dispatch
,然后数据到视图的渲染由 React/Vue 处理的。
(图片来自 这里)
// reducers.js
const initialState = {
loading: false
};
export default function reducer(state = initialState, action) {
switch (action.type) {
case "CHANGE_LOADING":
return {
loading: action.payload
};
default:
return state;
}
}
Saga
当你代码中有大量的异步操作时,例如 fetch 请求,你肯定会想到事件监听
、回调函数
、发布/订阅
。
很好,上一个例子其实就是事件监听
的处理方式,然后回调函数
的主流的解决方案是 redux-thunk,而发布/订阅
的主流解决方案是 saga。
import { takeLatest, put } from "redux-saga/effects";
import fetch from "./fetch";
function* fetchInfo(action) {
yield put({
type: "CHANGE_LOADING",
payload: true
});
yield fetch();
yield put({
type: "CHANGE_LOADING",
payload: false
});
}
export default function* fetchSaga() {
yield takeLatest("FETCH_REQUEST", fetchInfo);
}
完整演示
当你耐心看到这里,我知道你对 React 肯定有一定的经验,现在还可以做很多,例如把 loading 状态提升到 Store 的顶部,那整个站点就只有一个 loading 了,然后你还可以将 fetch 再封装一个 HOC 修改 loading 状态,这就是一个相对完美的 loading,其实 React 业务开发都可以用这个套路。
新的 API
Context
上面 redux 的例子是不是过于复杂
对于简单的业务,虽然有很多页面,嵌套层次也很复杂,你当然可以不用状态管理工具,你可以试着使用 Context,它可以方便你传递数据,它其实就是 Render Props 的一种实现。
export default React.createContext({
loading: false,
changeLoading: () => {}
});
Hooks
写到这,静一下,是不是哪里做错了什么?
我的业务只是想写个简单的 loading 效果,却了解了一堆组件生命周期的概念。
Hooks 刚好帮你解决了这样的问题,Hooks 能允许你通过执行单个函数调用来使用函数中的 React 功能,让你把面向生命周期编程
变成面向业务逻辑编程
。
import React, { useState, useEffect } from "react";
import Loading from "./Loading/index";
import fetch from "./fetch";
function App() {
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch().then(() => {
setLoading(false);
});
}, []);
return ;
}
export default App;