# 什么是 React 组件
组件本质上就是类和函数,不同的是组件承载了渲染视图的 UI 和更新视图的 setState 、 useState 等方法。
React 组件本质—— UI + update + 常规的类和函数 = React 组件
# 那么,函数组件和类组件本质的区别是什么呢?
对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state 等状态。对于每一次更新只需要调用 render 方法以及对应的生命周期就可以了。
但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。
# 组件通信方式
- props 和 callback 方式
- 父组件 -> 通过自身 state 改变,重新渲染,传递 props -> 通知子组件
- 子组件 -> 通过调用父组件 props 方法 -> 通知父组件。
- ref 方式。
- React-redux 或 React-mobx 状态管理方式。
- context 上下文方式。
- event bus 事件总线。
- 致命缺点
- 需要手动绑定和解绑。
- 对于小型项目还好,但是对于中大型项目,这种方式的组件通信,会造成牵一发动全身的影响,而且后期难以维护,组件之间的状态也是未知的。
- 一定程度上违背了 React 数据流向原则。
# State
state 到底是同步还是异步的? 回答出 batchUpdate 批量更新概念,以及批量更新被打破的条件。
dispatch 更新特点
- 函数组件更新就是函数的执行,在函数一次执行过程中,函数内部所有变量重新声明,所以改变的 state ,只有在下一次函数组件执行时才会被更新。
- 不要传入相同的 state,会导致视图不更新
const handleClick = () => { // 点击按钮,视图没有更新。 state.name = "Alien"; dispatchState(state); // 直接改变 `state`,在内存中指向的地址相同。 dispatchState({ ...state }); // 重新分配内存空间 };
# useState 原理
setState 和 useState 有什么异同?
相同点:
- 首先从原理角度出发,setState 和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。
不同点:
- 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
- setState 有专门监听 state 变化的回调函数 callback,可以获取最新 state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
- setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。
# Props
什么是 props ?
无论是函数组件还是类组件 父组件在子组件的标签里绑定的属性或者方法 最终都会变成 props 传给子组件
props 可以是什么?
- props 作为一个子组件渲染数据源。
- props 作为一个通知父组件的回调函数。
- props 作为一个单纯的组件传递。
- props 作为渲染函数。
- render props , 和 ④ 的区别是放在了 children 属性上。
- render component 插槽组件。
隐式注入 props
React.cloneElement(prop.children, { mes: "let us learn React !" });
# lifeCycle
一句话概括如何选择 useEffect 和 useLayoutEffect :修改 DOM ,改变布局就用 useLayoutEffect ,其他情况就用 useEffect 。
执行顺序 useInsertionEffect > useLayoutEffect > useEffect
|--------问与答---------|
问:React.useEffect 回调函数 和 componentDidMount / componentDidUpdate 执行时机有什么区别 ?
答:useEffect 对 React 执行栈来看是异步执行的,而 componentDidMount / componentDidUpdate 是同步执行的,useEffect 代码不会阻塞浏览器绘制。在时机上 ,componentDidMount / componentDidUpdate 和 useLayoutEffect 更类似。
|---------end----------|
# componentDidMount 替代方案
React.useEffect(() => {
/* 请求数据 , 事件监听 , 操纵dom */
}, []); /* 切记 dep = [] */
# componentWillUnmount 替代方案
React.useEffect(() => {
/* 请求数据 , 事件监听 , 操纵dom , 增加定时器,延时器 */
return function componentWillUnmount() {
/* 解除事件监听器 ,清除定时器,延时器 */
};
}, []); /* 切记 dep = [] */
# componentWillReceiveProps 替代方案
- 首先因为二者的执行阶段根本不同,一个是在 render 阶段,一个是在 commit 阶段。
- 其次 useEffect 会初始化执行一次,但是 componentWillReceiveProps 只有组件更新 props 变化的时候才会执行。
React.useEffect(() => {
console.log("props变化:componentWillReceiveProps");
}, [props]);
# componentDidUpdate 替代方案
useEffect 是异步执行,componentDidUpdate 是同步执行 ,但都是在 commit 阶段 。
React.useEffect(() => {
console.log("组件更新完成:componentDidUpdate ");
}); /* 没有 dep 依赖项 */
# Ref
{
current:null , // current指向ref对象获取到的实际内容,可以是dom元素,组件实例,或者其它。
}
# forwardRef 转发 Ref 解决 ref 不能跨层级捕获和传递的问题。
forwardRef 的初衷就是解决 ref 不能跨层级捕获和传递的问题。
# 函数组件 forwardRef + useImperativeHandle
对于函数组件,本身是没有实例的,但是 React Hooks 提供了,useImperativeHandle 一方面第一个参数接受父组件传递的 ref 对象,另一方面第二个参数是一个函数,函数返回值,作为 ref 对象获取的内容。一起看一下 useImperativeHandle 的基本使用。
useImperativeHandle 接受三个参数:
第一个参数 ref : 接受 forWardRef 传递过来的 ref 。 第二个参数 createHandle :处理函数,返回值作为暴露给父组件的 ref 对象。 第三个参数 deps :依赖项 deps,依赖项更改形成新的 ref 对象。
// 子组件
function Son(props, ref) {
const inputRef = useRef(null);
const [inputValue, setInputValue] = useState("");
useImperativeHandle(
ref,
() => {
const handleRefs = {
onFocus() {
/* 声明方法用于聚焦input框 */
inputRef.current.focus();
},
onChangeValue(value) {
/* 声明方法用于改变input的值 */
setInputValue(value);
},
};
return handleRefs;
},
[]
);
return (
<div>
<input placeholder="请输入内容" ref={inputRef} value={inputValue} />
</div>
);
}
const ForwarSon = forwardRef(Son);
// 父组件
class Index extends React.Component {
cur = null;
handerClick() {
const { onFocus, onChangeValue } = this.cur;
onFocus(); // 让子组件的输入框获取焦点
onChangeValue("let us learn React!"); // 让子组件input
}
render() {
return (
<div style={{ marginTop: "50px" }}>
<ForwarSon ref={(cur) => (this.cur = cur)} />
<button onClick={this.handerClick.bind(this)}>操控子组件</button>
</div>
);
}
}
# 逻辑流程图
# Context
提供者 Provider 的用法。
const ThemeContext = React.createContext(null); //
const ThemeProvider = ThemeContext.Provider; //提供者
const ThemeConsumer = ThemeContext.Consumer; // 订阅消费者
export default function ProviderDemo() {
const [contextValue, setContextValue] = React.useState({
color: "#ccc",
background: "pink",
});
return (
<div>
<ThemeProvider value={contextValue}>
<Son />
</ThemeProvider>
</div>
);
}
消费者 Consumer 三种用法
类组卷
this.context
函数组件
useContext(ThemeContext)
Consumer 方式
const ThemeConsumer = ThemeContext.Consumer; // 订阅消费者 function ConsumerDemo(props) { const { color, background } = props; return <div style={{ color, background }}>消费者</div>; } // 将 context 内容转化成 props const Son = () => ( <ThemeConsumer> {(contextValue) => <ConsumerDemo {...contextValue} />} </ThemeConsumer> );
# 模块化 CSS
CSS Modules
import style from "./style.css"; export default () => ( <div> <div className={style.text}>验证 css modules </div> </div> );
CSS IN JS
import React from "react"; import Style from "./style"; export default function Index() { return ( <div style={Style.boxStyle}> <span style={Style.textStyle}>hi , i am CSS IN JS!</span> </div> ); }
# HOC
HOC 的产生根本作用就是解决大量的代码复用,逻辑复用问题。、
那么具体复用了哪些逻辑呢?
- 本质上是对渲染的控制,对渲染的控制可不仅仅指是否渲染组件,还可以像 dva 中 dynamic 那样懒加载/动态加载组件。
- 还有一种场景,比如项目中想让一个非 Route 组件,也能通过 props 获取路由实现跳转,但是不想通过父级路由组件层层绑定 props ,这个时候就需要一个 HOC 把改变路由的 history 对象混入 props 中,于是 withRoute 诞生了。所以 HOC 还有一个重要的作用就是让 props 中混入一些你需要的东西。
- 还有一种情况,如果不想改变组件,只是监控组件的内部状态,对组件做一些赋能,HOC 也是一个不错的选择,比如对组件内的点击事件做一些监控
下面对 HOC 具体能实现那些功能,和如何编写做一下总结:
- 强化 props ,可以通过 HOC ,向原始组件混入一些状态。
- 渲染劫持,可以利用 HOC ,动态挂载原始组件,还可以先获取原始组件的渲染树,进行可控性修改。
- 可以配合 import 等 api ,实现动态加载组件,实现代码分割,加入 loading 效果。
- 可以通过 ref 来获取原始组件实例,操作实例下的属性和方法。
- 可以对原始组件做一些事件监听,错误监控等。