【React 基础及高级用法】
- React 基础
- React 浅析
- 传统的方式下,前端是如何构建用户界面的?
- 按照这种逻辑,可以写一个 createElement
- 回头看一下 react
- React 到底是什么?
- 前端在干什么?
- React 的灵活性
- 创建 react 工程
- React 的基础能力
- 子父组件
- State 和 Props
- 类组件
- 函数组件
- 子父组件传值的 props
- 条件与列表
- React 高级
- 一些 use API
- useState
- useEffect
- useLayoutEffect
- useInsertionEffect
- useEffect 如何模拟生命周期
- Ref
- Ref 的创建
- 类组件 - createRef
- 函数式组件 - useRef
- Ref 的常见使用方式
- Context
- 类组件—context
- 函数组件—useContext
- Hoc
- 优化相关 - useCallBack, useMemo, React.Memo
- React 的更新逻辑
React 基础
React 浅析
传统的方式下,前端是如何构建用户界面的?
• JS:叫做 浏览器脚本
○ 本质上我就是在前端的 html 页面上去操作 DOM 的。
○ JS 是一种可以操作 DOM,对 DOM 进行增删改查的语言。
按照这种逻辑,可以写一个 createElement
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><!-- 有一段 dom --><div id="root"><div class="title">你好</div><button>click me</button></div><!-- JavaScript 可以 操作 DOM,所以对于我这段 html, 我是不是可以只用 JavaScript 来实现 --></body>
<style>.title {font-size: 24px;font-weight: 700;}
</style>
<script>const div = document.createElement('div');const body = document.getElementsByTagName('body')[0];div.innerText = '你好';div.setAttribute('id', 'app');div.setAttribute('class', 'title')body.appendChild(div);document.getElementById('app').addEventListener('click', () => {console.log('hello luyi')});/**这样的话,我们尝试实现一个函数假如 我要书写一段这样的 HTML (DOM tree)<div id="root"><div class="title">你好</div><button>click me</button></div>// 理论上,我就可以用一段 createElement 的嵌套来表达。createElement('div', { id: 'root' }, createElement('div', {'class': 'title','textContext': '你好'}), createElement('button', {'style': 'background-color: blue','textContext': 'click me','onclick': '() => {}'}))*/function createElement(type, params, ...children) {// 1. 构建元素let ele;if(type.toLowerCase() === 'div') {ele = document.createElement('div')};if(type.toLowerCase() === 'button') {ele = document.createElement('button')};if(type.toLowerCase() === 'script') {// ....};// 2. 设置属性for(let key in params) {if(key === 'textContent') {ele[key] = params[key];} else if( ['onClick', 'onMouseDown'].includes(key) ) {// ... 事件的处理const method = key.toLowerCase().split('on')[1];ele.addEventListener(method, params[key]);} else {ele.setAttribute(key, params[key])}};// 3. 处理递归逻辑children.forEach(child => ele.appendChild(child));return ele;};const divRoot = createElement('div', {id: 'root'}, createElement('div', {class: 'title',textContent: '你好'}), createElement('button', {style: 'background-color: blue',textContent: 'click me',onClick: () => { console.log('hello luyi') }}));body.appendChild(divRoot)// ******* react babel 编译结果function App() {return React.createElement("div", {id: "root"}, React.createElement("div", {"class": "title"}, "\u4F60\u597D"), React.createElement("button", null, "click me"));}</script>
</html>
回头看一下 react
• 为什么 JSX 可以返回 HTML?
• 为什么说 React 是一个运行时框架?和 vue 对比 , vue 是一个编译型框架?
○ 编译是我去转化代码,AST,编译完了,我才能丢到引擎(V8)里去执行。
tips:
我们学习框架,一定要立体,要从多个维度去认识。
深度,不止是源码,还有发展历程和设计思想。
Babel ->
○ @babel/preset-react
○ @babel/preset-typescipt
○ @babel/preset-env
React 到底是什么?
function App() {return <div><div className="title">你好</div><button>click me</button><Submodule name={'xxx'} /></div>
};function Submodule ({ name }) {return <div>submodule--{name}</div>
}在 函数的情况下
- 支持使用 hooks 来表达,函数组件的状态和更新方式;
- jsx 足够的灵活,能够提供更好的动态化能力;class Submodule {render() {return <div>submodule</div>}
}在 类组件的情况下
- 支持生命周期,和状态表达,让我们更新界面。
前端在干什么?
React 的灵活性
template><div><div v-for="item of list" key="item">{{ item.name }}</div></div>
</template><div>{[...list].map(item => ({...item})).map(item => <div>{item.name}</div>)}
</div><div>{(() => {for(item of list) {}return results})()}
</div>// React 是不知道当前的改变,到底会有什么副作用的...this.setState -> callback
useEffect
unbatchedUpdate
创建 react 工程
npx create-react-app demo-app
npx create-react-app demo-app-ts --template typescript
React 的基础能力
子父组件
• 从 UI 的视角看
○ Class 组件渲染的是 render 函数中返回的内容;
○ Function 组件渲染的是,函数本身返回的内容;
State 和 Props
在 react 中,有一个概念叫做 state
• 如果我有一个数据,并且这个数据改变时,我需要触发界面更新。
○ 我要把这个数据定义成 state
○ 我要使用特殊的方法,去更新这个 state
类组件
setState 的方法。
类组件上,要用 setState
• State 的值,互相不影响
• setState 第二个参数是 一个 callback, 能拿到最新的 state 的值
import React, { Component } from 'react'const SubModule = () => <div>submodule</div>export default class ClassCom extends Component {constructor(props) {super(props);this.state = {number: 0,msg: 'hello luyi'}this.title = '类组件: state 的用法'}handleClick = (type) => {this.setState({number: this.state.number + (type === 'plus' ? 1 : -1)})};handleChange = (e) => {this.setState({msg: e.target.value})}handleAddFn = () => {this.setState({number: this.state.number + 1})}handleMinusFn = function() {this.setState({number: this.state.number - 1})}// 生命周期render() {const { number, msg } = this.state;return (<div><h2>{this.title}</h2><div>this is the number: {number}</div>{/* bind, call, apply */}<button onClick={this.handleClick.bind(this, 'plus')}>+</button><button onClick={() => this.handleClick('minus')}>-</button>{/* this 绑定的问题 */}<button onClick={this.handleAddFn}>+</button><button onClick={this.handleMinusFn.bind(this)}>-</button>{/* 受控组件的问题 */}<input value={msg} onChange={this.handleChange} /><input /><hr/><SubModule /></div>)}
}
函数组件
useState
[state, dispatch] = useState(initState);
• State 作为组件的状态,提供给UI 渲染视图
• Dispatch, 用户修改state 的方法,同时触发更新
○ Dispatch 的参数可以是函数,可以不是,如果是函数,就更新为函数执行的结果,如果不是,直接更新为值;
○ initState :初始值
可以是函数,可以不是,如果是函数,就更新为函数执行的结果,如果不是,直接是值;
import React, { useState } from 'react'export default function FunCom( {name }) {const title = '函数组件: state 的用法';const [number, setNumber ] = useState(0);const [msg, setMsg] = useState('hello ');const handleClick = (type) => {setNumber(number + (type === "plus"?1:-1))}const handleChange = (e) => {setMsg(e.target.value)}return (<div><h2>{title}</h2> <div>{number}</div><input value={msg} onChange={handleChange} /><button onClick={() => handleClick('plus')}>+</button><button onClick={handleClick.bind(null, 'minus')}>-</button></div>)
}
子父组件传值的 props
传值: 父到子
传函数: 子到父
条件与列表
import React, { useState } from 'react'const Hide = () => <div>to title</div>const Title = ({title}) => title.length ? <h3>{title}</h3> : <Hide />export default function Other() {const list = ['luyi', 'yunyin', 'xianzao'];const [show, setShow] = useState(false);return (<div><h2>条件与列表</h2><Title title={show? "数据管理": ''} /><button onClick={() => setShow(!show)}>{show?"setHide":"setShow"}</button><ul>{list.map(item => <li key={item}>{item}</li>)}</ul><ol>{(() => {let res = [];for(let item of list) {res.push(<li key={item}>{item}</li>)}return res})()}</ol></div>)
}
React 高级
一些 use API
useState
useEffect
useEffect(() => destory, deps)
- Callback: () => destory,是第一个参数,是一个 callback 函数
○ Destory:作为这个 callback 的返回值,会在callback 执行之前调用,用于清除上一次 callback 的副作用;
○ deps:第二个参数,是一个数组,数组中的值发生变化的话,会执行这个 callback 返回的 destory,然后再执行这个 callback 函数。
useLayoutEffect
- 同步执行
- useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前执行的,可以方便的修改DOM
- 如何选择:如果修改DOM,就用 useLayoutEffect,否则就用 useEffect
useInsertionEffect
是在 DOM 更新之前执行的,那么 对于 css-in-js 的场景,可以解决性能问题。
import React, { useEffect, useInsertionEffect, useState } from 'react'const getBookList = () => new Promise((resolve, reject) => {setTimeout(() => {resolve(['React 开发实战', 'Vue 原理解析', 'Webpack 从入门到精通']);}, 1000)
});const useBookList = () => {const [list, setList] = useState([]);useAsyncEffect(async () => {const _list = await getBookList();setList(_list);})return [list, setList];
};const useAsyncEffect = (cb) => {async function fetchData() {await cb();}fetchData();
}export default function Effect() {const [num, setNum] = useState(0);const [list, setList] = useState([]);useEffect(() => {setNum(list.length);},[list]);// 相当于是 mounted, componentDidMount 生命周期useEffect(() => {getBookList().then(res => {setList(res);})},[])useInsertionEffect(() => {const style = document.createElement("style");style.innerHTML = `.xxx {color: blue}`;document.head.appendChild(style);})return (<div><h3>the length is {num}</h3><ul>{list.map((item) => <li key={item}>{item}</li>)}</ul><button onClick={() => setList(['1','2','3'])}>setList</button></div>)
}
useEffect 如何模拟生命周期
import React, { useEffect, useState } from 'react'export default function LifeCycleMock(props) {const [state, dispatch] = useState(() => {console.log('getDerivedStateFromProps')return ''});useEffect(() => {console.log('componentDidMount');// 数据请求new Promise((resolve) => {setTimeout(() => {resolve('luyi')}, 300)}).then(res => {dispatch(res);});return () => {// 做一些监听器的销毁console.log('componentWillUnmount')};// deps 没有任何依赖,也就意味着,我只在初始化的时候,执行一次,destory 函数,只在销毁的时候执行一次}, []);useEffect(() => {console.log('componentDidUpdate')});useEffect(() => {console.log('componentWillReceiveProps');// 外面会传过来一个数据,同时我内部会想要自己可以处理}, [props]);return (<div>LifeCycleMock</div>)
}
Ref
Ref 的创建
类组件 - createRef
import React, { Component, createRef } from 'react';export default class ClassRef extends Component {constructor(props) {super(props);// 用来拿到某一个元素,组件的“句柄”/“实例”this.eleRef = createRef();this.inputRef = createRef();}handleFocus = () => {this.inputRef.current.focus()}handleClick = () => {console.log(this.eleRef.current)}render() {return (<div> <h3>Class Ref</h3><div id='usingRef' ref={this.eleRef} >eleRef</div><input ref={this.inputRef} /><button onClick={this.handleFocus}>focus</button><button onClick={this.handleClick}>click</button></div>);}
}
函数式组件 - useRef
import React, { useRef } from 'react'export default function FuncRef() {const inputRef = useRef(null);const eleRef = useRef(null);const handleClick = () => {inputRef.current.focus()}return (<div><h3>function Ref</h3><input ref={inputRef} /><button onClick={handleClick}>focus</button></div>)
}
Ref 的常见使用方式
- 解决闭包陷阱问题;
- 组件封装;
Visible 是谁的属性
• 是弹窗的属性
• 提供一种能力,让外面可以调用我的方法。
• forwardRef, 本质是处理子父组件之间的 ref 传递。
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react'export default function VisibleApi() {const modalRef = useRef(null);const inputRef = useRef(null);return (<div><button onClick={() => modalRef.current.setVisible(true)} >显示</button><button onClick={() => inputRef.current.focus()} >focus</button><FancyModal ref={modalRef} /><FancyInput ref={inputRef} /></div>)
}const Modal = (props, ref) => {const [vis, setVis] = useState(true);const setVisible = (val) => setVis(val);const getData = () => "hello"useImperativeHandle(ref, () => ({setVisible,getData}))return <divstyle={{ display: vis?'block':'none', position:'relative', height: '200px',background: '#ffeedd' }}><button style={{ position: 'absolute', right: '10px', top: '10px'}} onClick={() => setVis(false)}>close</button><div>我是一个模拟弹窗</div></div>
}const Input = (props, ref) => {return <input ref={ref} />
}const FancyModal = forwardRef(Modal);const FancyInput = forwardRef(Input);
Context
类组件—context
函数组件—useContext
import React, { createContext, useContext } from 'react'// 组件使用一个 withRouter 的函数包裹了以后,props 上面就能拿到 history 对象。
// 就是把 history 通过 context 传递了下去,并且使用 context 封装了一个高阶组件。const NavContext = createContext(null);
const history = window.history;export default function FuncContext() {return (<NavContext.Provider value={history}><Parent /></NavContext.Provider>)
};const Parent = () => <><Child1 /><WithRouterChild2 name={'luyi'} /></>const Child1 = (props) => {// 我通过 useContext, 是能拿到 最外层传入的 context 的值的。const his = useContext(NavContext);return <button onClick={() => his.pushState({}, undefined, 'hello')}>nav to hello</button>
}const Child2 = ({ name, his}) => {return <button onClick={() => his.pushState({}, undefined, 'hello')}>nav to hello -- {name}</button>
}const withRouter = (Component) => {return (props) => {const his = useContext(NavContext);return <Component {...props} his={his}/>}
};
// WithRouterChild2 是一个组件。Child2 也是一个组件。那么:
// withRouter 这个函数,接收一个组件,并且返回一个组件。
// 高阶函数:参数可以是函数,返回值也可以是函数
// 高阶组件:参数可以是组件,返回值也可以是组件
const WithRouterChild2 = withRouter(Child2);
Hoc
• 属性代理 – withRouter
• 反向继承 – LogProps
优化相关 - useCallBack, useMemo, React.Memo
React 的更新逻辑
// App -> FunctionComponent
// Fiber -> momezied -> Hook 链表 -> 为什么 useApi 不能有条件判断,const Demo = {Foo: Submodule
}function App() {useMemouseCallbackreturn <div><div className="title">你好</div><button>click me</button><Submodule name={'xxx'} /></div>
};// React.Memo
function Submodule ({ name }) {// 一定会执行,但是不代表 dom 会更新console.log('一定会执行')return <div>submodule--{name}</div>
}