闭关学习 react!
核心概念
jsx
1 | const name = "Josh Perez"; |
在大括号中可以填入任何有效的 javascript 表达式。
在编译之后,JSX 表达式会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象。
也就是说,你可以在 if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX:
React DOM 在渲染所有输入内容之前,默认会进行转义。这可以有效防止 XSS 攻击。
Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。他会创建这样一个虚拟 dom 对象。
1 | const element = { |
元素渲染
React 元素是不可变对象,一旦被创建,就无法更改他的子元素或者属性。更新 UI 的唯一方式就是创建一个新的元素,将其传入 ReactDOM.render()。
React 只更新它需要更新的部分。React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。
组件&props
函数组件,接受 props,返回 jsx
1 | function Welcome(props) { |
组件可以是用户自定义的组件,这时它会将 jsx 所接收的属性以及自组件转换为单个对象传给组件。组件必须以大写字母开头。
1 | function Welcome(props) { |
解构写法
1 | ReactDOM.render(<Comment {...data}>,document.getElementById('root')); |
props 应该是只读的,函数组件都应该为纯函数
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
当然,应用程序的 UI 是动态的,并会伴随着时间的推移而变化。在下一章节中,我们将介绍一种新的概念,称之为 “state”。在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。
组件的运作方式
- render 发现一个用户自定义组件,如果标签名是大写字母开头的,就会认为它是一个用户自定义组件
- 先把 jsx 属性封装为一个 props 对象
- 把它作为参数传递给组件
- render 方法会把次 react 元素渲染到页面上
state&生命周期
不要直接修改 state
1 | // Wrong |
而应该使用 setState()
1 | // Correct |
state 更新可能是异步的
不要依赖他们的值来跟新下一个状态
1 | // Wrong |
可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
1 | // Correct |
state 的更新可以被合并
数据向下流动
state 是局部的,除了拥有并且设置了它的组件,其他组件都无法访问。组件可以选择把它的 state 作为 props 向下传递给它的子组件中
事件处理
react 事件命名采用小驼峰。
使用 jsx 语法时,需要传入一个函数作为事件处理函数,而不是一个字符串,不能使用 return false 的方式组织默认行为,必须显式使用 preventDefault。
传参的方式
默认会将 e 作为最后一个参数传递,如果使用箭头函数的方式绑定,需要使用如下形式
1 | (e) => handleFunc(e); |
条件渲染
if && || 三目运算符
阻止组件渲染
让 render 函数返回 null
列表&key
渲染多个组件
使用 map 数组方法,渲染列表时应该提供一个 key 属性,通常使用数据中的 id 来作为元素的 key。
表单
受控组件
在 HTML 中,表单元素(如 input、 textarea 和 select)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
1 | class NameForm extends React.Component { |
textarea
在 html 中,textarea 使用子元素定义其文本,在 react 中,使用 value 属性代替
select
在 html 中 使用 selected 表明属性默认选中,react 在根 select 标签使用 value 属性
input type=file
在 react 中,它是一个非受控组件
处理多个输入
当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作。
受控输入空值
在受控组件上指定 value 的 prop 会阻止用户更改输入。如果你指定了 value,但输入仍可编辑,则可能是你意外地将 value 设置为 undefined 或 null
如果确实是只读属性,应当加上 readOnly 属性
1 | ReactDOM.render(<input value="hi" />, mountNode); |
状态提升
传递共同父组件的 props,props 是只读的,要在子组件中改变它,通常使用受控组件来解决,将父组件中改变 state 的函数作为 props 传递给子组件调用。
组合&继承
使用组合而非继承实现组件之间的代码重用
类似于 vue 的 slot,默认放在组件内部会作为 props.children 传递给子组件,当需要区分的时候,需要自定约定传入的 props
高级指引
无障碍
语义化
使用 React Fragments 来组合各个组件,类似于 Vue 中的 template
简写形式是 <></ >
其他内容看起来不是那么重要,略过
Context
react.createContext
创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。
1 | const MyContext = React.createContext(defaultValue); |
Class.contextType
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。不对其赋值是无法使用 this.context 获取值的。
refs 转发
refs 提供了一种方式,允许我们访问 DOM 节点或者在 render 方法中创建的 React 元素
基本用法 首先声明引用
1 | const ref = React.createRef(); |
然后再你希望获取引用的节点上
1 | <input type="text" ref={ref} value={props.content} /> |
这样在声明周期函数中,就可以使用 ref.current 获取到该节点的引用的
在高阶组件转发 refs
一般我们用高阶组件,会将所有 props 传递到其包裹的组件。但是 refs 不会传递,
1 | render() { |
可以使用 React.forwardRef API 明确地将 refs 转发到内部的组件。React.forwardRef 接受一个渲染函数,其接收 props 和 ref 参数并返回一个 React 节点。
1 | function logProps(Component) { |
如果不使用 React.forwardRef,普通的函数组件是不会获得该参数的,因为函数组件没有实例。
不管怎样,你可以在函数组件内部使用 ref 属性,只要它指向一个 DOM 元素或 class 组件:
使用 hooks
1 | function CustomTextInput(props) { |
现在有个问题,useRef 和 createRef 看起来没啥区别啊
百度之后得到的答案是 createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。
useRef 仅能用在 FunctionComponent,createRef 仅能用在 ClassComponent。!!!
错误边界
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
珠峰前端
生命周期详解
初始渲染阶段
componentWillMount -> render -> componentDidMount
更新阶段
shouldComponentUpdate
- 返回 true -> componentWillUpdate -> render -> componentDidUpdate
- 返回 false 不处理
从父组件传来的 props 改变
componentWillReceiveProps
- 返回 true -> shouldComponentUpdate -> 走上面的流程
- 返回 false 不处理
销毁阶段
componentWillUnmount
在这个生命周期勾子中清除定时器,防止内存泄漏

react16.3 生命周期的变化
我看的这个视频时 18 年的,在之后 react 的生命周期有一次大的变动,增加了 getDerivedStateFromProps 和 getSnapshotBeforeUpdate 这两个生命周期函数,同时 componentWillMount 和 componentWillReceiveProps 已经不推荐使用,将在未来的 react 版本中移除
getDerivedStateFromProps
这个生命周期会在每次 re-rendering 之前被调用
意味着即使你的 props 没有任何变化,而是父 state 发生了变化,导致子组件发生了 re-render,这个生命周期函数依然会被调用

PropTypes 类型检查
- array 数组
- bool 布尔值
- func 函数
- number 数字
- object 对象
- string 字符换
- symbol 符号
- node 任何节点 numbers strings elements 或者包含这些类型的数组或者片段
- element React 元素
- instanceOf(Message) 类的一个实例
- oneOf([‘News’,’Photos’]) 枚举值
- oneOfType([PropTypes.string,PropTypes.number]) 多种类型中其中之一
- arrayOf(PropTypes.number) 某种类型的数组
- objectOf(PropTypes.number) 某种类型的对象
- shape 特定形式的对象
- .required 必填
- function(){return new Error()} 自定义验证器
性能分析
分析性能 react 可以直接使用 react_perf,然后使用 chrome 进行录制分析
避免重复渲染
重写 shouldComponentUpdate 减少重新渲染
PureConponent
继承自 component,它做了一个优化,它重写了 shouleComponentUpdate 方法
这个比较是浅比较,所以 props 如果是对象,会出现无法更新的问题
1 | shouldComponentUpdate(nextProps,nextState){ |
immutable.js
可共享的变化是万恶之源,js 中的对象一般是可变的,因为使用引用赋值,对引用对象的修改会影响的原始对相关,为了避免这种影响,一般采用浅拷贝或者深拷贝的方式,但是如果对象的属性很多,就会造成 CPU 和内存的浪费。
immutable 中的数据一旦被创建,就不能被更改,所有修改和添加删除操作都会返回一个新的 immutable 对象。immutable 的实现原理是 Persistent Data Structure(持久化数据结构),这个咱之前也接触过,主席树的原理啊,抽空研究一下。
1 | let { Map } = require("immutable"); |
缺陷 体积过大 20k
seamiess-immutable
使用 Object.defineProperty 扩展的 Javascript 的 Array 和 Object 对象来实现,只支持 Array 和 Objcet 两种数据类型
hooks
useState
标准写法,参数就是 state 的初始值,解构得到的第二个变量是设置该 state 的函数
1 | const [age, setAge] = useState(42); |
useEffect
它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
当你调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。由于副作用函数是在组件内声明的,所以它们可以访问到组件的 props 和 state。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。
副作用函数还可以通过返回一个函数来指定如何“清除”副作用。例如,在下面的组件中使用副作用函数来订阅好友的在线状态,并通过取消订阅来进行清除操作:
使用规则
只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中。)
通过跳过 Effect 进行性能优化
1 | useEffect(() => { |
createPortal
将组件渲染到组件树之外的地方,可以用来做弹窗
diff 算法处理 key
// TODO
希望操作最小化
第一步,把老数组在新数组中没有的元素移除掉
React Fiber
React render
触发 render 的场景
- 状态更新时,类组件调用 setState 或者 函数组件的 state 更新
- 父容器重新渲染,无论子组件的实现如何,都会重新渲染
性能优化
谨慎分配 state,为了避免不必要的 render,可以将状态下放到更低层级的组件
合并状态更新
使用 PureComponent 和 React.memo 避免不必要的 render 调用
避免组件不必要的渲染的方法有:React.memo 包裹的函数式组件,继承自 React.PureComponent 的 class 组件。
浅比较
用 PureComponent 或者 React.memo 修饰的组件,每次更新的时候都对 props 和 state 进行一次浅比较。如果有对象,且对象引用变化,就会触发重渲染。由于函数也是引用类型,如果传递的是箭头函数(匿名函数),组件也会在父组件重新渲染的时候重新渲染。
useMemo
1 | function Example(props) { |