react基础
如何使用react
- Quick start和api Reference和installation的概念
写React工程时, 并不会用到所有的api 只会用到特别常用的api
- 使用方式
- script标签引入
- 脚手架使用
- react核心包
- React
- React.createElement("div",{},"hello react") -> ReactEl
- ReactDOM
- ReactDOM.createRoot(el ) // 创建工作节点
- root.render(ReactEl) // 渲染页面
- React
核心功能放在React内, 不包含任何宿主环境代码(document.getElementById)
React将对宿主操作能力放到ReactDOM中 唤醒相机,录音放在ReactNative中处理
- jsx使用 一种表达html结构的语法
- React.createElement("div",{},"hello react") -> 传入的对象
- {onClick:()=>{}} => 也就是说,给ReactEl加上属性 [class -> className]
- 由于多个dom被React.createElement会导致不好书写而且麻烦,则有jsx
- 使用jsx前提, 导入babel解析(语法降级)jsx文件
- script挂上type="text/babel",表达是将代码交给babel解析, 默认值为text/javascript
- 浏览器只会解析script的type为text/javascript和module,而babel解析type为text/babel中的代码,通过转换后加入新的script标签并写入其中
- 用书写jsx的方式创建ReactEl
- // 创建工作节点 const root = ReactDOM.createRoot(document.getElementById("root")); // 传入ReactEl const reactEl = ( <div> 123<h1>456</h1> </div> ); root.render(reactEl);
- React.createElement("div",{},"hello react") -> 传入的对象
React-cli脚手架的使用
- 什么是脚手架,提供一系列预设,让开发者无需考虑除了自身意外的其他外部问题
- npx -> npm的附带产物
-
去执行一段命令, npx会先看第一个参数是否有安装,没有则用npm临时安装,再执行后续命令
textnpx create-react-app my-app
-
React组件
- 组件允许我们将UI划分为独立的,可重用的部分
- 要求
- 组件名必须大写(小写为ReactEl,大写为组件)
- 组件必须返回ReactEl
- null/ReactEl/组件/可迭代对象
- 组件状态
- 组件内部数据发生变化,如何使页面发生变化
- 渲染 === 函数组件执行
- 改变了页面依赖的值,要重新渲染函数组件 -> [count, setCount] = useState(0)
- 组件状态发生变化
- 组件属性发生变化
- 组件状态更新是异步的,状态的更新是批量进行的
- 组件内部数据发生变化,如何使页面发生变化
- 组件属性
- 给组件传递属性就相当于给函数传递参数
React事件机制
- jsx语法 -> babel -> React.createEl -> 真实dom
- jsx转化的真实dom身上的事件监听不会绑定在任何dom上,React会保存jsx的身上对应注册的事件,并在root上处理
- React事件池机制 -> e.target无法被捕捉(共用event提高性能)
受控标签和非受控标签
- 受控标签:状态完全由他人控制,自身没有任何决定的权力
- 受控属性 -> value
- 非标签组件:自由的
- 只能通过defaultValue设置初始值,监听事件获取内部值
hooks使用之useState
hooks:允许开发者在不写类组件下的情况下去生成state以及一些其他曾经是类组件专属的东西
useState是一个react的hook, 让我们可以在组件内定义状态
- useState(initState) -> initState如果是函数的话, 必须是纯函数, 参数不会解析
- react严格模式
- immutable state 不可变状态, 每一次状态传递都是不可变的
hooks使用之useEffect
用来处理副作用
http请求/dom操作
-
useEffect(setup, dependencies?)
- setup初始化
- dependencies? 依赖数组
-
执行时机:
- 当我们使用useEffect去注册setup,React会在该组件每次挂载完毕到页面时会执行setup函数,异步执行
- 当依赖项发生变更时, 会执行对应setup
-
应用场景
- http请求
-
清除副作用
- 组件被卸载时内部的监听该销毁
- setup函数有一个返回值,为清理函数,会在组件销毁时执行(销毁监听事件,定时器)
textuseEffect(() => { const defaultValue = 10; setCount(defaultValue); const handleKeyDown = (e) => { console.log(e.code); }; window.addEventListener("keydown", handleKeyDown); return () => { window.removeEventListener("keydown", handleKeyDown); }; }, [count]);
hooks自定义实现之useRequestLoading()
textexport default function useRequestLoading(PromiseFn) { const [loading, setLoading] = useState(false); const execRequset = async () => { setLoading(true); await PromiseFn(); setLoading(false); }; return { loading, execRequset, }; }
text// 原写法 const [useList, setUserList] = useState([]); const [loading, setLoading] = useState(false); const fetchUserList = async () => { setLoading(true); const useListResp = await getUserList(); setUserList(useListResp); setLoading(false); requestIdleCallback(() => {}); }; useEffect(() => { fetchUserList(); }, []); return ( <div> <h1>用户列表</h1> {loading ? ( <div>loading...</div> ) : ( useList.map((user) => <div key={user.id}>{user.name}</div>) )} </div> ); // hook写法 const [useList, setUserList] = useState([]); const { loading, execRequset } = useRequestLoading(); useEffect(() => { execRequset(async () => { const result = await getUserList(); setUserList(result); }); }, []); return ( <div> <h1>用户列表</h1> {loading ? ( <div>loading...</div> ) : ( useList.map((user) => <div key={user.id}>{user.name}</div>) )} </div> );
hooks自定义实现之useForceUpdate
textexport default function useForceUpdate() { const [_, setCompState] = useState({}); function forceUpdate() { setCompState({}); } return forceUpdate; }
hooks自定义实现之useScrollWatcher
textexport default function useMouseWatcher(moveCallback) { useEffect(() => { const removeEvent = () => { window.removeEventListener("mousemove", moveCallback); }; window.addEventListener("mousemove", moveCallback); return removeEvent; }, []); } // 实现 const [pos, setPos] = useState({ x: 0, y: 0 }); const removeEvent = useMouseWatcher((e) => { console.log("listender", e.pagx, e.pageY); setPos({ x: e.pageX, y: e.pageY }); }); return ( <div> <h2>滚动监听</h2> <button onClick={removeEvent}>移除监听</button> <div> pos:{pos.x}-{pos.y} </div> </div> );
内置hooks使用
用来长期稳定的维护某一个函数引用,
-
useCallback(initFn,depenece)
-
只有当依赖发生变化时,initFn的应引用才会改变
textconst addCount2 = useCallback(() => { console.log("执行了addCount2函数", count2); setCount2((pre) => pre + 1); }, [count]); // 当count发生变化时,addCount2的引用才刷新 -
dependness 依赖项发生变化以后,函数内引用会刷新
-
-
useMemo(initFn) -> 类似vue的计算属性
- 第一个参数是一个函数,会被React直接执行并缓存,
- 第二个为依赖 当以来发生变化时吗, 重新缓存
-
useRef -> 类似vue的 dom ref
<></> 语法糖 -> React.Fragment
-
构建一个状态,这个状态脱离react控制,他的变化不会造成组件重新渲染,同时状态不会再次初始化
textconst [timer, setTimer] = useState(60); let timerId = useRef(null); console.log("刷新了页面"); const start = useCallback(() => { console.log("点击了start"); timerId.current = setInterval(() => { setTimer((pre) => pre - 1); }, 1000); }, []); const stop = useCallback(() => { if (timerId.current) clearInterval(timerId.current); }, [timerId]); -
挂真实dom,自动在useEffect内获取当前dom
textimport React, { useRef } from "react"; export default function TestInput() { const inputElRef = useRef(); const focus = () => { inputElRef.current.focus(); }; return ( <> <input ref={inputElRef} type="text" /> <button onClick={focus}>focus input!</button> <hr /> </> ); } -
可读可写的,
-
-
ForwordRef() -< 给函数组件扩展一个ref属性作为第二参数
-
useImperativeHandle 当以来发生变化时, 才会给ref.current刷新值
- 第一个参数ref
- 第二个参数fn -< 返回值为ref.current
- 第三个参数为依赖项
-
useContent
解决props以外的数据共享,将多层组件数据共享简化, 用来做全局数据共享的
- 通过ThemeContent.Provider传递给内容上下文
- 通过ThemeContent.Provide传递value
- 后代组件通过useContent(ThemeContent), 获取value
- 通过ThemeContent.Provider传递给内容上下文
-
useLayoutEffect()
- useEffect方法当首次渲染工作完成并将真实dom生成页面以后, 并肩对应回调函数推入异步队列等待执行
- useLayoutEffect方法当首次渲染工作完成并将真实dom生成页面以后, 并肩对应回调函数推入同步队列等待执行
浏览器渲染帧探究
- 浏览器一帧需要做的事
- 处理上一帧的用户交互事件
- 调用requestAnimationFrame
- 执行重排重绘
- 调用requestIdleCallback
- 掉帧
- react调度机制
React渲染原理之concurrency(并发性, 可中断渲染)
- React渲染一个组件到页面中要做哪些事情
- 拿到React.createElement所返回的ReactEl节点 -> 是个对象
- 通过render进行渲染
- 如果是react元素节点的话就生成dom插入 appendChild
- 如果是组件这会在渲染保存对应的hooks以及触发对应的hooks
- 更新 -> diff
- react将渲染流程分为了两个阶段
- render:负责将需要渲染的组件的内部逻辑以及react内部逻辑并执行得到一份fiber清单[记录了要展示的真是dom树是什么样子,要增加哪些dom]
- commit:负责将fiber清单转换成真实dom