Taro 4.0 特性更新
- 依赖升级
- NodeJs v18
- React Native v0.73
- 其他三方依赖
- Vite 支持
- 性能提升
- compilerMode 小程序编译模式 github.com/NervJS/taro…
-
长列表渲染 taro会比小程序原生写法多出其他节点, 通过开启小程序编译模式,分期代码生成小程序的代码模板,以此来减少生成多余节点。适用场景是节点较复杂且大量重复的场景
这几种语法,小程序会生成一些额外的节点。
<template>wx:ifwx:for
缺点
- 小程序编译模式使用了空间来换时间,编译出模板会令包体积增大,开发者应酌情使用
- 编译阶段只能识别、优化部分语法
- 针对不同的开发框架 DSL,Taro 需要分别去做适配,如 JSX、Vue template 等,工作量会比较大
- 鸿蒙支持
Vue 和 React 差异点
- Vue 的 tempalte 和 React 的 jsx - 写法区别
- 组件复用 - 写法区别
- 数据流 - 双向绑定只是针对表单组件的一个语法糖,都是一样的单向数据流。
- Vue 的 单文件组件 和 React 的 组件 - 写法区别,React也可以实现jsx里写css
实际差异
-
监听
-
2.0 的 defineProperty 还是 3.0 的 Proxy 都是针对每个状态的原子化监听,非常精确;React是函数式编程,局部刷新;
-
vue 是基于原子级别的数据侦听,react 是基于组件级别的重新渲染。
-
但是React也暴露出来一些比如 shouldComponentUpdate、 PureComponent 和 memo 等一些hook 来主动去判断是否需要刷新(React 需要做的更多)。
-
React的hook不能写在条件判断语句中,因为它底层是一个链表,需要依赖hook的顺序,如果因为条件判断导致中间缺少或者增多hook,会导致顺序错乱而出错。
-
Vue的监听就不会有这种问题。但是大型项目下,Vue的这种原子化监听也会给项目带来性能负担。并且它没有类似React的fiber有针对任务优先级的划分,可能就会导致UI卡顿。
-
-
事件代理系统
-
React 的事件系统并不是原生的事件,而是自己包装的合成事件,比如onClick 是由 click 合成,onChange 是由 blur ,change ,focus 等多个事件合成。
-
目的是为了兼容全浏览器,以此抹平不同浏览器的差异。v17前是将事件都挂载在document上,之后是挂在实例容器上(利于多实例场景,微前端)。
-
-
预编译优化
- Vue 需要进行数据拦截或代理,那它就需要在预编译阶段静态分析模版,分析出视图依赖了哪些数据,进行响应式处理。所以它可以将静态节点当作字符串处理,移出dom diff的过程。
- React 则由于没有这种原子化监听,它只能整体更新diff。但React在与Prepack合作,它可以将普通的javascript 代码等价优化为更简洁的代码,以此减少运行时的运算成本。
React 相关知识点
1. 老版本的 React 中,为什么写 jsx 的文件要默认引入 React?
因为 jsx 在被 babel 编译后,写的 jsx 会变成上述 React.createElement 形式,所以需要引入 React,防止找不到 React 引起报错。
2. React.Children.toArray 方法可以将 React 子元素扁平化为一个数组。返回的是一个新的数组,而不会修改原始的子元素。可以更方便的操作子元素。
3. React.isValidElement 检查一个对象是否为有效的 React 元素。(不能用于检查普通的 JavaScript 对象或数组。)
import React from 'react';
function MyComponent(props) {
const { children } = props;
return (
<div>
{React.Children.map(children, child => {
if (React.isValidElement(child)) {
return child;
}
})}
</div>
);
}
4. React.cloneElement 复制并返回一个新的react元素
- element(React 元素):要克隆的 React 元素。
- props(对象):要传递给克隆元素的新属性。这个参数是可选的。
- children(React 元素):要作为克隆元素的子元素传递的内容。这个参数也是可选的。
React.cloneElement(element, props, children)
5. 那么,函数组件和类组件本质的区别是什么呢?
对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state 等状态。对于每一次更新只需要调用 render 方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。
6. 类组件如何限制 state 更新视图,避免不必要的更新
PureComponent 会在 shouldComponentUpdate 生命周期方法中进行浅比较,以确定是否需要重新渲染组件。
import React, { PureComponent } from 'react';
class MyComponent extends PureComponent {
render() {
return <div>{this.props.text}</div>;
}
}
7. setState
- 调用 setState 方法,实际上是 React 底层调用 Updater 对象上的 enqueueSetState 方法。
- 在react的事件系统中,多个setState会进行合并state,事件执行前会打开批量更新,结束后关闭。
- 在setTimeout中可以打破批量更新的规则,这个时候setState会变成同步代码。
- unstable_batchedUpdates可以手动开启批量更新,他接收一个函数,函数内执行setState会打开批量更新。
- ReactDOM.flushSync 可以提升更新优先级(并合并前面的state)。
flushSync 中的 setState > 正常执行上下文中 setState > setTimeout ,Promise 中的 setState。
8. 类组件中的 setState 和函数组件中的 useState 有什么异同?
相同点:
- 首先从原理角度出发,setState和 useState 更新视图,底层都调用了 scheduleUpdateOnFiber 方法,而且事件驱动情况下都有批量更新规则。
不同点
- 在不是 pureComponent 组件模式下, setState 不会浅比较两次 state 的值,只要调用 setState,在没有其他优化手段的前提下,就会执行更新。但是 useState 中的 dispatchAction 会默认比较两次 state 是否相同,然后决定是否更新组件。
- setState 有专门监听 state 变化的回调函数 callback,可以获取最新state;但是在函数组件中,只能通过 useEffect 来执行 state 变化引起的副作用。
- setState 在底层处理逻辑上主要是和老 state 进行合并处理,而 useState 更倾向于重新赋值。
9. 在组件实例上可以通过 _reactInternals 属性来访问组件对应的 fiber 对象。在 fiber 对象上,可以通过 stateNode 来访问当前 fiber 对应的组件实例。
10. 生命周期
11. useEffect、useLayoutEffect
**一句话概括如何选择 useEffect 和 useLayoutEffect :修改 DOM ,改变布局就用 useLayoutEffect ,其他情况就用 useEffect 。**
12. React.useEffect 回调函数 和 componentDidMount / componentDidUpdate 执行时机有什么区别 ?
useEffect 对 React 执行栈来看是异步执行的,而 componentDidMount / componentDidUpdate 是同步执行的,useEffect代码不会阻塞浏览器绘制。在时机上 ,componentDidMount / componentDidUpdate 和 useLayoutEffect 更类似。
13. hook 替代 class 生命周期 useEffect 代替 componentDidMount / componentWillUnmount
```js
React.useEffect(()=>{
/* componentDidMount */
/* 请求数据 , 事件监听 , 操纵dom , 增加定时器,延时器 */
return function componentWillUnmount(){
/* 解除事件监听器 ,清除定时器,延时器 */
}
},[])
```
没有第二个参数,那么每一次执行函数组件,都会执行该 effect。
```js
function FunctionLifecycle(props){
const [ num , setNum ] = useState(0)
React.useEffect(()=>{
/* 请求数据 , 事件监听 , 操纵dom , 增加定时器 , 延时器 */
console.log('组件挂载完成:componentDidMount')
return function componentWillUnmount(){
/* 解除事件监听器 ,清除 */
console.log('组件销毁:componentWillUnmount')
}
},[])/* 切记 dep = [] */
React.useEffect(()=>{
console.log('props变化:componentWillReceiveProps')
},[ props ])
React.useEffect(()=>{ /* */
console.log(' 组件更新完成:componentDidUpdate ')
})
return <div>
<div> props : { props.number } </div>
<div> states : { num } </div>
<button onClick={ ()=> setNum(state=>state + 1) } >改变state</button>
</div>
}
export default ()=>{
const [ number , setNumber ] = React.useState(0)
const [ isRender , setRender ] = React.useState(true)
return <div>
{ isRender && <FunctionLifecycle number={number} /> }
<button onClick={ ()=> setNumber(state => state + 1 ) } > 改变props </button> <br/>
<button onClick={()=> setRender(false) } >卸载组件</button>
</div>
}
```
14. ref 3种获取方式
- 字符串
- 函数
- ref对象
15. context
const ThemeProvider = ThemeContext.Provider;
//提供者
export default function ProviderDemo(){
const [ contextValue , setContextValue ] = React.useState({ color:'#ccc', background:'pink' })
return <div>
<ThemeProvider value={ contextValue } >
<Son />
</ThemeProvider>
</div>
}
类组件消费 contextType
const ThemeContext = React.createContext(null); // 类组件 - contextType 方式
class ConsumerDemo extends React.Component{
render(){
const { color,background } = this.context
return <div style={{ color,background } } >消费者</div>
}
}
ConsumerDemo.contextType = ThemeContext const Son = ()=> <ConsumerDemo />
函数式组件 useContext
const ThemeContext = React.createContext(null)
// 函数组件 - useContext方式
function ConsumerDemo(){
const contextValue = React.useContext(ThemeContext) /* */
const { color,background } = contextValue
return <div style={{ color,background } } >消费者</div>
}
const Son = ()=> <ConsumerDemo />
context 存在的问题,子元素会跟着状态的改变而全部更新 解决方法:
const Son = React.memo(()=> <ConsumerDemo />)
// 或
<ThemeProvider value={ contextValue } >
{ React.useMemo(()=> <Son /> ,[]) }
</ThemeProvider>
16. CSS Modules
composes组合方式
.base{ /* 基础样式 */
color: blue;
}
.text { /* 继承基础样式 ,增加额外的样式 backgroundColor */
composes:base;
background-color: pink;
}
// 可以引用其他文件中的类名
.text { /* 继承基础样式 ,增加额外的样式 backgroundColor */
composes:base from './style1.css'; /* base 样式在 style1.css 文件中 */
background-color: pink;
}
其他方式:CSS IN JS
17. Hooks
-
① React Hooks 为什么必须在函数组件内部执行? React 如何能够监听 React Hooks 在外部执行并抛出异常。
- 引用的 React hooks都是从 ReactCurrentDispatcher.current 中获取的, React 就是通过赋予 current 不同的 hooks 对象达到监控 hooks 是否在函数组件内部调用。默认赋值给错误类型的对象,调用就会报错。
-
② React Hooks 如何把状态保存起来?
- 保存的信息存在了哪里?对于函数组件 fiber ,用 memoizedState 保存 hooks 信息。
-
③ React Hooks 为什么不能写在条件语句中?
- 因为fiber上的hooks会保存成一个链表,如果中间的顺序不确定,则可能出现对应不上的问题,导致复用错误。
-
④ useMemo 内部引用 useRef 为什么不需要添加依赖项,而 useState 就要添加依赖项。
- ref本身就是一个创建和复用的过程,所以加了依赖项也没什么用。
-
⑤ useEffect 添加依赖项 props.a ,为什么 props.a 改变,useEffect 回调函数 create 重新执行。
-
⑥ React 内部如何区别 useEffect 和 useLayoutEffect ,执行时机有什么不同?
- React 就是在 commit 阶段,通过标识符,证明是 useEffect 还是 useLayoutEffect ,接下来 React 会同步处理 useLayoutEffect ,异步处理 useEffect
18.自定义hook
如何使用自定义hook 获取数据 www.robinwieruch.de/react-hooks…
自定义 hook 配合 useReducer 来实现数据获取的自定义hook
// useDataApi.js
const dataFetchReducer = (state, action) => {
switch (action.type) {
case 'FETCH_INIT':
return {
...state,
isLoading: true,
isError: false
};
case 'FETCH_SUCCESS':
return {
...state,
isLoading: false,
isError: false,
data: action.payload,
};
case 'FETCH_FAILURE':
return {
...state,
isLoading: false,
isError: true,
};
default:
throw new Error();
}
};
const useDataApi = (initialUrl, initialData) => {
const [url, setUrl] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
isError: false,
data: initialData,
});
useEffect(() => {
const fetchData = async () => {
dispatch({ type: 'FETCH_INIT' });
try {
const result = await axios(url);
dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
} catch (error) {
dispatch({ type: 'FETCH_FAILURE' });
}
};
fetchData();
}, [url]);
};
组件内消费
// APP.jsx
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
import useDataApi from './useDataApi'
function App() {
const [query, setQuery] = useState('redux');
const [{ data, isLoading, isError }, doFetch] = useDataApi(
'https://hn.algolia.com/api/v1/search?query=redux',
{ hits: [] },
);
return (
<Fragment>
<form
onSubmit={event => {
doFetch(
`http://hn.algolia.com/api/v1/search?query=${query}`,
);
event.preventDefault();
}}
>
<input
type="text"
value={query}
onChange={event => setQuery(event.target.value)}
/>
<button type="submit">Search</button>
</form>
{isError && <div>Something went wrong ...</div>}
{isLoading ? (
<div>Loading ...</div>
) : (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
)}
</Fragment>
);
}
export default App;