32. React
32.1 引入React
第一种方法:
script标签引入
一般不用这个方法
cjs, umd是什么: 模块定义 第二种方法: webpack 方法, 也很麻烦
第三种: create-react-app
目标一: 用cdn方法引入react , 渲染一个可以增加的n
渲染失败:
const React = window.React;
const ReactDOM = window.ReactDOM;
let n = 0;
const App = React.createElement("div", null, [
n,
React.createElement(
"button",
{
onClick: () => {
n += 1;
console.log(n); //这一句是精髓
ReactDOM.render(App, document.querySelector("#app")); // 为什么还是不能重新渲染
}
},
"+1"
)
]);
ReactDOM.render(App, document.querySelector("#app"));
渲染成功:
const React = window.React;
const ReactDOM = window.ReactDOM;
let n = 0;
const App =()=> React.createElement("div", null, [
n,
React.createElement(
"button",
{
onClick: () => {
n += 1;
console.log(n); //这一句是精髓
ReactDOM.render(App(), document.querySelector("#app"));
}
},
"+1"
)
]);
ReactDOM.render(App(), document.querySelector("#app"));
变量不会重新执行, 函数每次调用就会重新执行
为啥let 不打印6个6;
立即求值, 延迟求值:
虚拟DOM
找到虚拟DOM不同的算法: DOM Diff算法
Facebook babel 作者招进来, 然后让他将jsx集成如babel.js
方法一的缺陷: 在浏览器端, 下载, 翻译jxs, 非常慢
全都要学react 和vue
32.2 组件和元素
32.2.1 元素和组件的区别
元素: 也就是虚拟DOM.
const App = React.createElement("div", null, null)
组件: 返回元素的一个函数, vue里面一个构造选项可以表示一个组件.
const App = ()=>React.createElement("div", null, null)
32.2.2 函数组件和类组件的区别
函数组件的写法:
function Welcome(props) {
return <h1>Hello,{props.name}</h1>
}
//调用方法:
<Welcome name="frank"/>
Welcome({"name":"frank"})
类组件的写法:
class Welcom extends React.Component{
render(){
return <h1>Hello,{this.props.name}</h1>
}
}
//调用方法:
<Welcome name="frank"/>
32.2.3 Babel理解jsx的逻辑
我们的代码:
function Welcome(props) {
return <h1>Hello,{props.name}</h1>
}
Babel理解的:
function Welcome(props) {
return /*#__PURE__*/React.createElement("h1", null, "Hello,", props.name);
}
可以通过babel online查看babel的理解:https://babeljs.io/repl
React.createElement 的逻辑
- 如果传入一个字符串
div
, 就会创建一个虚拟dom. - 如果传入一个函数, 就会调用函数,获取其返回值
- 如果传入一个类, 就会调用一个new, 新建一个对象, 调用对象的render方法, 获取其返回值
32.2.4 用两种方法创建组件
function App() {
let messageForSon ="儿子好"
return (
<div className="App">
爸爸
<Son messageForSon={messageForSon} />
</div>
);
}
class Son extends React.Component{
constructor() {
super();
this.state ={
n:0
}
}
add(){
this.setState({n:this.state.n +1})
}
render(){
return(
<div className="Son">
消息:{this.props.messageForSon}
儿子 n:{this.state.n}
<button onClick={()=>this.add()}>+1</button>
<GrandSon messageForSon='孙子好'/>
</div>
);
}
}
const GrandSon =(props)=>{
const [n,setN]=React.useState(0)
return(
<div className="Grandson">
消息:{props.messageForSon}
孙子 n:{n}
<button onClick={()=>setN(n+1)}>+1</button>
</div>
)
}
踩的坑:
保存:
react-dom.development.js:13231 Uncaught Error: Objects are not valid as a React child (found: object with keys {n}). If you meant to render a collection of children,
原因: jsx语法会自动调用React.CreateElements, 所{this.state}是一个对象, 作为参数就会报错:Objects are not valid as a React child .
//错误的:
return(
<div id="son">
儿子 n:{this.state}
<button onClick={()=>this.add()}>+1</button>
</div>
);
//正确的:
return(
<div id="son">
儿子 n:{this.state.n}
<button onClick={()=>this.add()}>+1</button>
</div>
);
踩的坑:
报错: react一直加载不出来, 感觉像是服务器死机了
原因:在定义GrandSon的时候,里面引用了GrandSon,造成了死循环,死机
const GrandSon =()=>{
const [n,setN]=React.useState(0)
return(
<div className="Grandson">
孙子 n:{n}
<button onClick={()=>setN(n+1)}>+1</button>
这里是错的:
<GrandSon/>
</div>
)
}
32.2.5 使用props
//方法一: 传递
<Son messageForSon={messageForSon} />
//方法一: 接收
<div className="Son">
消息:{this.props.messageForSon}
儿子 n:{this.state.n}
<button onClick={()=>this.add()}>+1</button>
<GrandSon messageForSon='孙子好'/>
</div>
//方法二:传递
<GrandSon messageForSon='孙子好'/>
//方法二:接收
const GrandSon =(props)=>{
const [n,setN]=React.useState(0)
return(
<div className="Grandson">
消息:{props.messageForSon}
孙子 n:{n}
<button onClick={()=>setN(n+1)}>+1</button>
</div>
)
}
32.2.6 使用state
//方法一:
constructor() {
super();
this.state ={
n:0
}
}
add(){
this.setState({n:this.state.n +1})
}
//方法二:
const GrandSon =(props)=>{
const [n,setN]=React.useState(0)
return(
<div className="Grandson">
消息:{props.messageForSon}
孙子 n:{n}
<button onClick={()=>setN(n+1)}>+1</button>
</div>
)
}
为什么不能直接修改n, 因为react没有监听n, 必须使用setState告诉浏览器进行重新渲染
因为setState是异步方法, 所有这样写更好
//原来的
this.setState({n:this.state.n +1}) //不会马上修改state, 等会再修改
//新的
this.setState(state=>{
return {n:state.n+1}
}
)
函数组件setState不会改变n, 而是产生一个新n
this.State({n:1})不会修改其他属性. 但是this.State({n:1, user:{name:1,age:16}}) 但是user不会合并以前数学
这种写法不推荐: 因为修改n,会修改m
const [state, setState] = React.useState({
n:0, m:0
});
32.2.7 事件绑定
用函数组件, 就不会被this折磨
this的面试题需要画画.
32.3 组件
32.3.1 创建class组件的两种方式
创建class组件
过时的:
import React from 'react'
const A = React . createclass ({
render(){
return(
<div>hi</div>
)
}
})
class组件:
class Son extends React.Component{
constructor(props) {
super(props);
this.state ={
n:0
}
}
add(){
this.setState({n:this.state.n +1})
}
render(){
return(
<div className="Son">
消息:{this.props.messageForSon}
儿子 n:{this.state.n}
<button onClick={()=>this.add()}>+1</button>
<GrandSon messageForSon='孙子好'/>
</div>
);
}
}
注意的几点:
- extends、constructor、super强行记忆,别问为什么
- 不能改props, 外部数据应该由外部更新
- componentWillReceiveProps: props更新的回调函数, 已经过时了, 被弃用了
- props: 接收外部数据, 函数
- 钩子: 也就是特殊函数
32.3.2 setState:延迟更新陷阱
例如:
onClick = ()=>{
this.setState({
x:this.state.x+1,//2
})
this.state.x//1
this.setState({
x:this.state.x+1,//2
})
}
因为onClick中的语句执行完, 才会更新state. 导致**闹钟同时响效应*
解决办法: 函数形式写更新state
onClick2 = ( ) => {
this.setState((state)=>({x:state.X+1}))
this.setState((state)=>({x:state.X+1}))
}
注:this.setState(???,fn)
,成功更新后,会调用fn函数
####32.3.3 生命周期函数
函数列表:
- constructor()
- shouldComponentUpdate()
- render()
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
constructor
用途:
-
初始化 props
-
初始化state,但此时不能调用setState
-
用来写 bind this
constructor(){ /*其他代码略*/ this.onClick = this.onClick.bind(this) }
-
可不写
shouldComponentUpdate
目标一: 阻止render
- ·用途: 返回true表示不阻止UI更新 返回false表示阻止UI更新
- 面试常问 v问:shouldComponentUpdate有什么用? 答:它允许我们手动判断是否要进行组件更新,我们可以根据应用场景灵活地设置返回值,以避免不必要的更新
为什么说shouldComponentShould 会阻止render 而不是阻止渲染更新页面
如下图:
{n:1}, {n:1}地址不一样, 所有不是同一个对象, React会render, 但是render后生成的虚拟DOM一样,不会更新页面, shouldComponentUpdate,可以用来阻止render
具体代码:
shouldComponentUpdate(newProps,newState){
if(newState.n=this.state.n){
return false
}else{
return true
}
}
我们除了自己比较, 还有更简单的做法: 使用React.pureComponent
目标二: purecomponent 新旧state 对比
render:
易错:
-
render只能单个元素, 可以用React.Fragment标签包裹, 这个标签不会出现在html中. 简写
<></>
-
返回多个列表元素一定要加key
render(){ return this.state.array.map(n→<span key={n}>{n}</span>) }
componentDidMount
目标三: 获取挂载组件的宽度: getBoundingClient , 依赖Dom元素
先声明变量, 防止程序找不到
首次执行渲染会 执行componentDidMount
componentDidUpdate
-
**面试官会问:**可以在哪个生命周期发起ajax请求?追问还可以在哪?
一般在componentDidMount, 也可以在componentDidUpdate, 但是这里一般用于更新数据
-
在此处setState可能会引起无限循环,除非放在if里
-
若shouldCom...Update返回false,则不触发此钩子
componentWilUnmount
用途: 组件将要被移出页面然后被销毁时执行代码 unmount 过的组件不会再次 mount 举例
- 如果你在c..DidMount里面监听了window scroll 那么你就要在c..WillUnmount里面取消监听
- 如果你在c..DidMount里面创建了Timer 那么你就要在c..WillUnmount里面取消Timer
- 如果你在c..DidMount里面创建了AJAX请求 那么你就要在c..WillUnmount里面取消请求
- 否则你就是菜逼 原则:谁污染谁治理
钩子执行顺序:
目标四: 自己画一下钩子执行顺序
前面加了 UNSAFE_ 前缀的生命周期,都是被弃用的
32.4 函数组件
创建方式
const Hello = props =><div>{props.message}</div>
函数组件模拟生命周期:
useEffect的作用
- 解决副作用: 当渲染一次, 就会重新执行组件里面所有代码
- 生命周期的作用
目标一: 测试渲染调用所有代码
import React from 'react'
const Test =()=>{
const [n, setN] = React.useState(0)
const addN =()=> setN(n+1)
console.log("执行了一次")
return (
<>
<div>n: {n}</div>
<div>
<button onClick={addN}>+1</button>
</div>
</>
)
}
export default Test
副作用: 每次更新state都会执行函数组件里面的代码, 就算虚拟Dom一样, 也会重新执行, useEffect用于指定哪些函数变量,才会执行哪些代码
useEffect模拟声明周期
useEffect
-
模拟componentDidMount
useEffect(()=>{ console.log('第一次渲染')},[])
-
模拟 componentDidUpdate
useEffect(()=>{ console.log('任意属性变更')})
useEffect(()=>{ console.log('n变了')},n)
-
模拟 componentWillUnmount
useEffect(()=>{ console.log('第一次渲染') return()=>{ console.log('组件要死了') } }
目标二: 模拟 willUnmount
import React,{useEffect}from 'react'
const Test =()=>{
const [childVisble, setchildVisble] = React.useState(true)
const changechildVisble =()=> setchildVisble(!childVisble)
return (
<>
{childVisble?<Child/>:null}
<div>
<button onClick={changechildVisble}>show</button>
<button onClick={changechildVisble}>hide</button>
</div>
</>
)
}
const Child =()=>{
useEffect(()=>{
console.log("组件变化了")
return ()=>{
console.log("组件挂了")
}
})
return(
<div>child</div>
)
}
export default Test
目标三: 自定义hook, 实现只在第二次渲染,执行
import React,{useEffect,useState}from 'react'
const Test =()=>{
const [n, setN]= useState(0)
const addN =()=> setN(n+1)
const useUpdate =(fn, m)=>{
const [count, setcount]= useState(0)
useEffect(()=>setcount(count+1),[m])
useEffect(()=>{
if(count>1){
fn()
}
},[count])
}
useUpdate(()=>console.log("我不在第一次执行"),n)
return (
<>
<div>n: {n}</div>
<div>
<button onClick={addN}>+1</button>
</div>
</>
)
}
export default Test
32.5 Hooks 原理解析
问自己几个问题
-
执行setN的时候会发生什么?n会变吗?App()会重新执行吗?
- setN一定会修改数据X,将n+1存入X
- setN 一定会触发<App/>重新渲染(re-render)
-
如果App()会重新执行,那么 useState(0)的时候,n每次的值会有不同吗?
useState 肯定会从×读取n的最新值
32.5.1 简易版useState
代码如下:
let _state
const myUseState =(initValue)=>{
//!!!! 这里不用||保底值, 考虑到_State为0时,会被迫初始化
_state = _state === undefined ? initValue:_state
const setState=(newState)=>{
_state = newState
//这里可以让函数重新渲染
render()
}
//!!!! 这里返回值的写法
return [_state, setState]
}
//!!!! 这里注意渲染函数的简单实现
const render =()=>ReactDOM.render(
<Test/>,
document.getElementById('root')
);
const Test =()=>{
const [n, setN]= myUseState(0)//useState(0)
const addN =()=> setN(n+1)
return (
<>
<div>n: {n}</div>
<div>
<button onClick={addN}>+1</button>
</div>
</>
)
}
export default Test
32.5.2 解决两个state不兼容的问题
先看这一段代码:
let index = 0
const f1=()=>{
let current1 = index
const f2 = ()=>{
console.log(current1)
}
index +=1
return f2
}
let cur1 = f1()
let cur2 = f1()
cur1() // 0
cur2() // 1
cur1() // 0
cur2() // 1
当f1执行完, 局部变量current1 生命周期结束. current在f2中被固定为常量, 只要调用curl1 , 里面的current1永远是0, 和外面的current1已经没有关系了
具体看兼容代码:
let _state = []
let index = 0
const myUseState =(initValue)=>{
const currentIndex = index
_state[currentIndex] = _state[currentIndex] === undefined ? initValue:_state[currentIndex]
const setState=(newState)=>{
//!!!! curreantIndex 被setState 固定下来
_state[currentIndex] = newState
render()
}
index +=1
return [_state[currentIndex], setState]
}
const render =()=> {
index = 0
ReactDOM.render(
<Test/>,
document.getElementById('root')
)
}
useState不能放在if里面
例如代码:
function App() {
const [n, setN] = React.useState(0);
let m, setM;
// useState 不能放在if函数里面.
if (n % 2 === 1) {
[m, setM] = React.useState(0)
}
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
<p>{m}</p>
<p>
<button onClick={() => setM(m + 1)}>+1</button>
</p>
</div>
);
}
否则会如下报错:
原因:
因为state存在数组里面, 不同的useState, 对应的index不同, 所以必须要确认一致的useState顺序
解决全局_State, index的问题:
总结:
- 每个函数组件对应一个React节点*
- 每个节点保存着state和index
- useState 会读取 state[index]
- index 由 useState 出现的顺序决定
- setState 会修改 state,并触发更新
32.5.3 setN分身问题
情景一:
- n=0, 然后:
const log = ( )=>setTimeout(()=>console.log("n:${n}",3000);
,设置三秒后打印n setN(n+1)
- 最后打印
n:0
原因: setN不改变N, n=0, n=1 都存在于内存中, 也就是setN分身问题
如何解决setN分身问题?
-
使用Ref: 能够在一个组件内重新渲染的时候,Ref的值不变
function App() { const nRef = React.useRef(0) // nRef: {current: 0} const log = ( )=>setTimeout(()=>console.log(`n:${nRef.current}`,3000); return ( <div className="App"> <p>{n}</p> <p> <button onClick={() => (nRef.current += 1)}>+1</button> </p> </div> ); }
但是Ref的值变化, 不会引起组件重新渲染
nRef.current: 变化不会重新渲染, 可以调用nRef, 重新调用一下setState:
+ const [n, setN]= useState(0) - <button onClick={() => (nRef.current += 1)}>+1</button> + <button onClick={() => {nRef.current += 1,setN(nRef.current)}}>+1</button>
-
useRef贯穿一个组件始终, useContext贯穿所有组件始终:
const themeContext = React.createContext(null); function App() { const [theme, setTheme] = React.useState("red"); return ( <themeContext.Provider value={{ theme, setTheme }}> <div className={`App ${theme}`}> <p>{theme}</p> <div> <ChildA /> </div> <div> <ChildB /> </div> </div> </themeContext.Provider> ); }
总结
- 每次重新渲染,组件函数就会执行
- 对应的所有state都会出现「分身」
- 如果你不希望出现分身
- 可以用 useRef/useContext等
32.7 redux
一个程序员不喜欢用原生js, 那你就推荐他使用vanilla js 这个库, 体积又小, 功能又强大.
count接收两个参数: state, action, 前者是之前的状态, 后者是要做的动作.
store: 存储state的地方, 获取state的方法: store.getState()
, 修改state的方法: store.dispatch({type:"add"})
不能在用钱规则counter里面使用setTimeout
state就像钱, store就像是我的管家, 我想查看我的金额, 存钱,取钱都需要通过管家store, counter就是之前定好的规则, 我想存钱取钱,通知管家, 管家就会按照之前定好的规则操作.
React-redux
能让你随时访问store , 不会混乱.
-
先用ProVided 任命store为App总管家
-
然后App组件, 以及子组件中, 通过connect告诉Store, 我需要哪些信息, 我血药哪些操作
-
然后再this.props中获取需要的东西
32.8 React Hook
####32.8.1 setState
注意:
- setState, userReducer都不会合并属性.
- 由于setN分身问题, 所以在
setN(n=>n+1)
里面尽量用匿名函数
32.8.2 useReducer
不合并属性, 需要用展开语法:
如何代替redux?
-
建立管家:
-
定好规则:
-
创建Context
const Context = createContext(null)
-
创建读写api:
-
将读写api 放到Context里面:
-
各个组件可以使用读写api:
数据更新的时候, Context是逐级通知的
32.8.3 useEffect
effective: 副作用: 对环境的改变就是副作用.
多个effect , 按照出现的顺序执行
useLayoutEffect
改变浏览器外观后执行useEffect, useLayoutEffect在实际Dom生成进行截胡, 执行完之后,才改变浏览器外观
大部分的时候不会在useEffect里面改变外观.但是会改变用户看到浏览器改变的时间
为了用户体验, 优先useEffect
32.8.4 useMemo
修改n, 但是Child只依赖m, 也会再执行一遍:
具体写法: 只要props 不变, 就不会重新执行
有一个bug , 传值没问题, 传函数地址就有问题:
解决方法: 函数也用useMemo包裹:
useCallback 是 useMemo的语法糖
32.8.5 useRef
组件重新渲染也不会变的变量:
改变count.current, 不会自动render
forwardRef
能够将ref参数传递到函数组件.
32.8.6 自定义Hook
把hook写在一起, 然后把读和写接口暴露出去:
尽量封装在一起, 不要在组件上面写一堆hook函数
32.8.7 stale closure
如果一个函数包含了一个闭包, 在执行多次这个函数, 将产生多个个不同的的闭包, 如果仅记住第一个闭包, 这个闭包有可能过时.
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!