认识react
- JSX 并不是一个新的模板语言,而可以认为是一个语法糖。
- React.createElement 作用就是创建一个组件的实例;参数:
- 第一个参数表示组件的类型;
- 第二个参数是传给组件的属性,也就是 props
- 第三个以及后续所有的参数则是子组件。
React.createElement(
"div",
null,
React.createElement(
"button",
{ onClick: function onClick() {
return setCount(count + 1);
} },
React.createElement(CountLabel, { count: count })
)
);
理解hooks
- hooks的好处:简化了逻辑复用。
如果用class组件实现逻辑复用需要封装高阶组件;有以下缺点:
- 代码难理解,不直观,很多人甚至宁愿重复代码,也不愿用高阶组件;
- 会增加很多额外的组件节点。每一个高阶组件都会多一层节点,这就会给调试带来很大的负担。
- 在 Class 组件中,代码是从技术角度组织在一起的,例如在 componentDidMount 中都去做一些初始化的事情。而在函数组件中,代码是从业务角度组织在一起的,相关代码能够出现在集中的地方,从而更容易理解和维护。
内置 Hooks
- React 提供的 Hooks 其实非常少,一共只有 10 个,比如 useState、useEffect、useCallback、useMemo、useRef、useContext 等等。
- 副作用是指一段和当前执行结果无关的代码。
- useEffect 让我们能够在下面四种时机去执行一个回调函数产生副作用:
- 每次 render 后执行:不提供第二个依赖项参数。比如useEffect(() => {})。
- 仅第一次 render 后执行:提供一个空数组作为依赖项。比如useEffect(() => {}, [])。
- 第一次以及依赖项发生变化后执行:提供依赖项数组。比如useEffect(() => {}, [deps])。
- 组件 unmount 后执行:返回一个回调函数。比如useEffect() => { return () => {} }, [])。
- Hooks 的使用规则包括以下两个:
- 只能在函数组件的顶级作用域使用;
- 只能在函数组件或者其他 Hooks 中使用。
- useCallback解决事件处理函数的问题:
- 不仅增加了系统的开销
- 每次创建新函数的方式会让接收事件处理函数的组件,需要重新渲染。
- useCallback和useMemo做了同一件事情:建立了一个绑定某个结果到依赖数据的关系。只有当依赖变了,这个结果才需要被重新得到
用useMemo实现useCallback:
const myEventHandler = useMemo(() => {
// 返回一个函数作为缓存结果
return () => {
// 在这里进行事件处理
}
}, [dep1, dep2]);
问题:useCallback/useMemo 什么情况下使用?是所有情况?还是个别情况,例如计算量较大等情况?
- useRef:
- 在多次渲染之间共享数据
使用 useRef 保存的数据一般是和 UI 的渲染无关的,因此当 ref 的值发生变化时,是不会触发组件的重新渲染的,这也是 useRef 区别于 useState 的地方。
- 保存某个 DOM 节点的引用
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// current 属性指向了真实的 input 这个 DOM 节点,从而可以调用 focus 方法
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
ref 这个属性提供了获得 DOM 节点的能力,并利用 useRef 保存了这个节点的应用。这样的话,一旦 input 节点被渲染到界面上,那我们通过 inputEl.current 就能访问到真实的 DOM 节点的实例了。
- useContext 定义全局状态
和全局的变量去保存数据的区别?
就是为了能够进行数据的绑定。当这个 Context 的数据发生变化时,使用这个数据的组件就能够自动刷新。但如果没有 Context,而是使用一个简单的全局变量,就很难去实现了。
- useContext 的缺点:
- 会让调试变得困难,因为你很难跟踪某个 Context 的变化究竟是如何产生的。
- 让组件的复用变得困难,因为一个组件如果使用了某个 Context,它就必须确保被用到的地方一定有这个 Context 的 Provider 在其父组件的路径上。
所以在 React 的开发中,除了像 Theme、Language 等一目了然的需要全局设置的变量外,我们很少会使用 Context 来做太多数据的共享。需要再三强调的是,Context 更多的是提供了一个强大的机制,让 React 应用具备定义全局的响应式数据的能力。
自定义hooks的使用场景:
- 抽取业务逻辑;
- 封装通用逻辑;
- 监听浏览器状态;
- 拆分复杂组件。
全局状态管理:如何在函数组件中使用 Redux?:
- 理解 Redux 的三个基本概念:State、Action 和 Reducer。
- 其中 State 即 Store,一般就是一个纯 JavaScript Object。
- Action 也是一个 Object,用于描述发生的动作。
- 而 Reducer 则是一个函数,接收 Action 和 State 并作为参数,通过计算得到新的 Store。
好处:1. 可预测性(Predictable):即给定一个初始状态和一系列的 Action,一定能得到一致的结果,同时这也让代码更容易测试。2. 易于调试:可以跟踪 Store 中数据的变化,甚至暂停和回放。因为每次 Action 产生的变化都会产生新的对象,而我们可以缓存这些对象用于调试。Redux 的基于浏览器插件的开发工具就是基于这个机制,非常有利于调试。
- React 和 Redux 共同使用时的单向数据流:
- 什么是异步action?Middleware 在 Action 真正到达 Reducer 之前提供的一个额外处理 Action 的机会:
Redux 中的 Action 不仅仅可以是一个 Object,它可以是任何东西,也可以是一个函数。利用这个机制,Redux 提供了 redux-thunk 这样一个中间件,它如果发现接受到的 action 是一个函数,那么就不会传递给 Reducer,而是执行这个函数,并把 dispatch 作为参数传给这个函数,从而在这个函数中你可以自由决定何时,如何发送 Action。
复杂状态处理:如何保证状态一致性?
- 一个是状态最小化原则,也就是说要避免冗余的状态;
- 另一个则是唯一数据源原则,避免中间状态。
函数组件设计模式:如何应对复杂条件渲染场景?
- 容器模式:实现按条件执行 Hooks
- 把条件判断的结果放到两个组件之中,确保真正 render UI 的组件收到的所有属性都是有值的。
- 还有一种做法,就是把判断条件放到 Hooks 中去。
-
使用 render props 模式重用 UI 逻辑
事件处理:如何创建自定义事件?
- React 原生事件的原理:合成事件(Synthetic Events)
由于虚拟 DOM 的存在,在 React 中即使绑定一个事件到原生的 DOM 节点,事件也并不是绑定在对应的节点上,而是所有的事件都是绑定在根节点上。然后由 React 统一监听和管理,获取事件后再分发到具体的虚拟 DOM 节点上。
在 React 17 之前,所有的事件都是绑定在 document 上的,而从 React 17 开始,所有的事件都绑定在整个 App 上的根节点上,这主要是为了以后页面上可能存在多版本 React 的考虑。
-
React 这么做的原因主要有两个:
- 虚拟 DOM render 的时候, DOM 很可能还没有真实地 render 到页面上,所以无法绑定事件。
- React 可以屏蔽底层事件的细节,避免浏览器的兼容性问题。同时呢,对于 React Native 这种不是通过浏览器 render 的运行时,也能提供一致的 API。
- 由于浏览器事件的冒泡模型。无论事件在哪个节点被触发, React 都可以通过事件的 srcElement这个属性,知道它是从哪个节点开始发出的,这样 React 就可以收集管理所有的事件,然后再以一致的 API 暴露出来。
- 虽然自定义事件和原生事件看上去类似,但是两者的机制是完全不一样的:
- 原生事件是浏览器的机制;
- 而自定义事件则是纯粹的组件自己的行为,本质是一种回调函数机制。
路由管理:为什么每一个前端应用都需要使用路由机制?
-
React Router 管理
这里需要注意,React Router 不仅支持浏览器,还支持 React Native,以及一些用 Web 实现的移动 App,所以它提供了多个 npm 模块。
- BrowserRouter、Link、Route、Switch 等组件的用法及作用。
- BrowserRouter:表示用标准的 URL 路径去管理路由,比如 /my-page1 这样的标准 URL 路径。除此之外,还有 MemoryRouter,表示通过内存管理路由;HashRouter,标识通过 hash 管理路由。我们自己实现的例子其实就是用的 hash 来实现路由。
- Link:定义一个导航链接,点击时可以无刷新地改变页面 URL,从而实现 React Router 控制的导航。
- Route: 定义一条路由规则,可以指定匹配的路径、要渲染的内容等等。
- Switch:在默认情况下,所有匹配的 Route 节点都会被展示,但是 Switch 标记可以保证只有第一个匹配到的路由才会被渲染。
- 使用嵌套路由:实现二级导航页面所谓嵌套路由,也称为子路由,就是一个页面组件内部,还需要通过 URL 上的信息来决定组件内部某个区域该如何显示。
- 在 URL 中保存页面状态
一方面可以提升用户体验,另一方面也可以简化页面之间的交互。
- 路由层面实现权限控制
我们完全可以利用前端路由的动态特性。你已经看到了,路由是通过 JSX 以声明式的方式去定义的,这就意味着路由的定义规则是可以根据条件进行变化的,也就是所谓的动态路由。
按需加载:如何提升应用打开速度?
- 如何实现按需加载?
使用 import 语句,定义按需加载的起始模块语法是 import(someModule)。
- 使用 service worker 缓存前端资源
- 缓存永远不过期。你只要下载过一次,就永远不需要再重新下载,除非主动删除。
- 永远不会访问过期的资源。换句话说,如果发布了一个新版本,那么你可以通过版本化的一些机制,来确保用户访问到的一定是最新的资源。
1.注册 Service Worker
在 Service Worker 安装之后初始化缓存机制
- 拦截请求
参考链接:cache mdn