React
为什么用
名称含义:re hack html
传统 web ,要更新一个状态,必须刷新整个页面,而重绘的伤害性体验,是传统 web 无法解决的
如何追踪变化?如何按需更新?
组件化,解决协作,复用问题
- VirtualDOM:JS 版的 DOM ,高效渲染 DOM(diff) ,允许服务端渲染
- diff 两个 VirtualDOM 树
- 改变节点类型,会略过 diff
函数就是组件
- 组件内任意嵌套组件
const c1 = props => <h1 {...props}>hello</h1>
<c1 style={{ color: 'red' }}/>
React 再加上函数式编程技巧,项目可以无往而不利
显示和状态分离
用 stateless 组件来负责显示,class 组件来负责状态和逻辑
是什么
最简单的理解,一个组件的渲染函数就是一个基于 state 和 props 的纯函数,state 是自己的,props 是外面来的,任何东西变了就重新渲染一遍,
学习参考
5 Steps for Learning React Application Development - Telerik Developer Network
Virtual DOM
- 两个前提:JS 高效,DOM 操作慢
- 生成虚拟 DOM 树,更新时对比,用最少的操作更新视图
state
state 计算依赖,依赖值
this.setState({ counter: this.state.counter + 1 }) // won't update this.setState(previousState => ({ counter: previousState.counter + 1 }))
state 可作为 props 向下传递,props 按组件树向下传递,而 state 由组件单独管理和由函数冒泡改变
布局总结
- react 宽度基于 pt 为单位, 可以通过 Dimensions 来获取宽高,PixelRatio 获取密度,如果想使用百分比,可以通过获取屏幕宽度手动计算。
- 基于 flex 的布局
- view 默认宽度为 100%
- 水平居中用 alignItems, 垂直居中用 justifyContent
- 基于 flex 能够实现现有的网格系统需求,且网格能够各种嵌套无 bug
- 图片布局
- 通过 Image.resizeMode 来适配图片布局,包括 contain, cover, stretch
- 默认不设置模式等于 cover 模式
- contain 模式自适应宽高,给出高度值即可
- cover 铺满容器,但是会做截取
- stretch 铺满容器,拉伸
- 定位
- 定位相对于父元素,父元素不用设置 position 也行
- padding 设置在 Text 元素上的时候会存在 bug。所有 padding 变成了 marginBottom
- 文本元素
- 文字必须放在 Text 元素里边
- Text 元素可以相互嵌套,且存在样式继承关系
- numberOfLines 需要放在最外层的 Text 元素上,且虽然截取了文字但是还是会占用空间
JSX
定义
像 XML 的 JavaScript 语法扩展,用于描述包含属性的树状结构
我们只要记住 HTML 只是代码创建 DOM 的一种语法形式,就很容易理解 JSX。而这种使用代码构建界面的方式,完全消除了业务逻辑和界面元素之间的隔阂,让代码更加直观和易于维护。
- 组件(首字母大写)
- HTML 标签(首字母小写)
创建和使用组件
// 创建
var MyComponent = React.createClass({
/*...*/
})
// 使用
var myElement = <MyComponent someProperty={true} />
ReactDOM.render(myElement, document.getElementById('example'))
命名组件解决子组件较多的情况
- 如何使用混合子表达式
- 如何添加注释
特殊属性:
- className 替换 class
- htmlFor 替换 for
可使用点号对象访问方式,构成组件名
const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>
}
}
function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />
}
首字母大写才会被解析为用户定义组件,否则会认为是普通 HTML 标签
const components = {
photo: PhotoStory,
video: VideoStory
}
function Story(props) {
const SpecificStory = components[props.storyType]
return <SpecificStory story={props.story} />
}
扩展属性
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />
}
function App2() {
const props = { firstName: 'Ben', lastName: 'Hector' }
return <Greeting {...props} />
}
0
会被渲染,使用逻辑表达式前要确保条件类型是布尔值
<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>
<div>
{props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>
render 中绑定函数提到外面
官方解答,使用箭头函数是 OK 的,在你遇到性能问题之前
Passing Functions to Components – React
原理:创建新函数,意味着 prop 值每次变化,进而不必要地 re render 子组件
- 外提箭头函数赋值
handleClick = () => {
console.log('clickity')
}
- 在构造器中统一绑定
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('clickity');
}
解决绑定传参,拆分组件
learn
state 每个组件私有
当需要获取多个子组件数据,或两个组件需要相互通讯,state 提升到父组件
受控组件
受控”与“非受控”两个概念,区别在于这个组件的状态是否可以被外部修改。一个设计得当的组件应该同时支持“受控”与“非受控”两种形式,即当开发者不控制组件属性时,组件自己管理状态,而当开发者控制组件属性时,组件该由属性控制。而开发一个复杂组件更需要注意这点,以避免只有部分属性受控,使其变成一个半受控组件。 受控组件与非受控组件 · 语雀
不可变数据:
跟踪更容易
优势,帮助创建 pure components,变化时机 --> 确定重新渲染
只有 render,不含私有数据(state),写成函数组件更简单
const NoStateComp = props => ( <div onClick={props.onClick}>{props.children}</div> )
React 元素 对象 一等公民 参数传递
动态列表,指定 key,且 key 一般不要用数组索引
组合,no 继承
大型 webapp 首选
组件划分原则,单一职责
渲染 UI vs 添加交互
大量编码 vs 大量细节
prop vs state
构建静态版本时,不需要 state
小型 vs 大型
自下而上 vs 自上而下
state 需要定义的最少数据
创建新上层组件理由,需要存放共同 state
- class ==> hook why
- 组件复用,抽象层嵌套地域
- 生命周期,不相关逻辑分散
- class this 理解难度大
hooks
- hooks 是一种在函数式组件内使用 state 逻辑,消除写 classes 的必要。
- vue 可以结合 mixin 来写无状态的函数式组件。mixin 的缺点是无法消费或使用另一个 mixin。导致链式逻辑难以实现。
- 更清晰定义和共享逻辑、传递 state
Hooks are coming to Vue.js version 3.0 - LogRocket BloguseHooks~小窍门 - 知乎
批判
观点:可组合性优于继承
当我们对复杂系统进行抽象时,我们并没有消除复杂性,而是移动了它。
尝试在组件的顶层进行状态更新将导致无限循环。状态更新重新运行组件。这并不意味着 DOM 更新,但它确实意味着另一个状态更新将触发另一个重新运行,这将触发一个状态更新,该状态更新将触发重新运行等等。
当您开始使用 React Context 并开始在父组件中发出更新信号时,事情会变得更加复杂。渲染级联。也许一个组件获取一些数据,一些组件重新安装,然后您再次运行状态更新,延迟了几秒钟。
VDOM 防止无关的 DOM 更新,而不是状态计算。
考虑迁移成本,依然是现实工作选择
React Is Holding Me Hostage --- React 挟持了我
useState
use 设置对象或数组时替换, 有别于 class 合并
除非需要替换更新的一类数据,否则都应该分开定义
Hooks FAQ – ReactReact Class features vs. Hooks equivalents • Soluto Engineering Blog无意识设计-复盘 React Hook 的创造过程 · Issue #4 · shanggqm/blog
调用限制:
只在 top level 调用 Hooks,而不能在循环、条件或嵌套函数中使用
只在 React 函数组件或自定义 Hooks 中调用,而不能在普通 JS 函数中
useState 里数据务必为 immutable
// bad 这样无法触发更新
setList(list.sort((a, b) => a - b));
// good 必须传入一个新的对象
setList(list.slice().sort((a, b) => a - b));
useEffect
async 要用 IIFE 包起来
组件渲染后执行,不阻塞绘制。
componentDidMount
andcomponentDidUpdate
是同步什么情况下不用?操作 DOM 且每次渲染不一样,DOM 更新后又触发 effect。用户会看到界面闪烁,只有这一种情况需要使用
useLayoutEffect
,执行在 dom 更新后,绘制前
Fragment 相当于 vue 的 template
加空格方式 {' '}
setState 更新回调
- componentDidUpdate
setState(updater, callback)
render return 里可直接定义变量const ChapterName = <h2>{chapterName}</h2>;
useEffect 滥用
最好的答案就在 React 官方文档内。我们团队把它浓缩成了几个简单的规则:
尽量不用 useEffect。
若是组件内部状态(useState/useMemo/useCallback)监听,若非必要,禁止使用useEffect/useLayoutEffect触发副作用,推荐放到onClick这类事件回调中触发副作用。
必须使用lint生成hooks的依赖项,否则需要加注释说明。
useEffect 中若使用了资源类操作(接口请求、订阅/事件、localStorage存储等),则务必返回销毁函数。
什么时候会用到 useEffect?需要对外部状态有相互影响的逻辑(副作用),才有必要放到useEffect/useLayoutEffect 中。
生命周期
React lifecycle methods diagram
componentDidUpdate
- 组件更新后立即调用,首次渲染不调用
- 用于网络请求导致 props 变化等
- 注意,执行操作要有条件,避免死循环
组件演化
PropTypes
默认非必须,即允许 null / undefined
Portal
脱离父组件的渲染,不受父级布局影响,常用于模态对话框、悬浮菜单
仅影响 DOM 结构,不影响事件、生命周期、组件树
React.memo vs useMemo
类型,顶层 api,hooks 第二参数,比较函数(是否相等),依赖数组
避免受
const propsAreEqual = (prevProp: any, nextProp: any) => {
const result = JSON.stringify(prevProp) === JSON.stringify(nextProp);
return result;
};
React.memo(Comp, propsAreEqual);
useMemo 缓存计算值,仅依赖变化时重新计算
React diff 算法
- 不同元素类型,旧元素卸载,挂载新元素
- 相同类型的 DOM 元素,只更新属性
- 相同类型的 Component 元素,更新 prop
- DOM 子元素遍历比较,顺序发生变化会全部认为 diff,可用 key 显式指明是否是同一元素
Reconciliation – ReactReact的思考(五)- Reconciliation - NO END FOR LEARNING
不可变数据优势 入门教程: 认识 React – React
- 易于实现复杂特性,如撤销和恢复
懒加载
React.lazy 配合 Suspense
setState 何时同步、何时异步
与 Vue 原理一样,由于是状态驱动视图,如果 state 短时间内频繁设置,是否要立刻响应渲染呢?当然不是,所以要在一次同步代码执行完、合并更新,在 React 中是等父子组件的内置事件监听器中的 setState 全部执行完。
nextjs
React 服务器组件也是如此。人们听到“服务器”并假设是 Node.js。但 RSC 可以在构建期间运行。事实上,在 Next 13 App Router 中它是默认值。如果你在 RSC 中获取()某些东西,它将在构建时,除非你选择动态渲染。
我们看到的转变(是的,正在努力实现)并不是从“编写 SPA”到“不编写 SPA”。它是从“被锁定在 SPA 中”(以后很难添加内置时间或服务器端集成)到“使用对每个页面有意义的任何模式”。hybrid era.
这是一种转变,但主要是一种心理转变。
Next.js SPA example with dynamic client-only routing and static hosting