【React 基础及高级用法】
【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 <div
style={{ 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() {
useMemo
useCallback
return <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>
}