react-beginner
本文最后更新于:2022年5月31日 下午
React
构建用户页面的 javascript 库,并不是完整的 MVC 框架
Intro
React.createElement
JSX 元素只是调用 React.createElement(component, props, ...children)
的语法糖
ReactDOM.render(element, container[, callback)
在提供的 container
里渲染一个 React 元素,并返回对该组件的引用(或者针对无状态组件返回 null)。
如果 React 元素之前已经在 container
里渲染过,这将会对其执行更新操作,并仅会在必要时改变 DOM 以映射最新的 React 元素。
如果提供了可选的回调函数,该回调将在组件被渲染或更新之后被执行。
JSX 语法
- 语法糖,更能体现 react 的声明式特点,逻辑和页面标签写在一起
- jsx 不是标准的 ECMAScript 语法,babel 编译
const title = (
// 推荐使用小括号包裹
<h1>我是个title</h1>
);
- jsx 中的属性名驼峰命名
-
{..中可以写js表达式,区别vue的双括号..}
条件渲染
// if else
if(){
return xxx
}else{
return xxx
}
// 三元表达式
return flag ? (.<dv>..) : (..<dv>.)
// 逻辑与运算符
return flag && (..<dv>.)
循环渲染
key 的作用和 vue 一样用于 diff 时复用节点
const data = [{}...{}...{}]
const list = (
{data.map(item=>{
return <div key={item.id}>{item.name}</div>
})}
)
样式
行内样式,双括号
const title = <h1 style={{ color: "red" }}>我是个title</h1>;
className
const title = <h1 className="xxx">我是个title</h1>;
组件是 react 一等公民
组件:复用,独立,组合
组件
函数组件(使用函数创建)
- 大写首字母
- 必须 return
- 包括箭头函数
function Com1() {
return <div>我是Com1</div>;
}
ReactDOM.render(<Com1 />, document.getElementById("app"));
babel 转译后,function 运行在严格模式下,严格模式的 this 指向 null
类组件
-大写首字母 - extends自`React.Component` - 必须return;
class MyCom2 extends React.Component {
render() {
return <div>我是Com2</div>;
}
}
「state」
- 一般写法
class Com3 extends React.Component{
constructor(){
this.state = {xxx:xxx}
this.func1 = this.func1.bind(this)
}
func1(){
this.setState(xxx:xxx)
// 需要手动派发 `setState()`
}
render() => <xxx onClick={this.func1}></xxx>
}
- 使用赋值语句简写
class Com3 extends React.Component{
state = {xxx:xxx}
func1 = () => {
this.setState(xxx:xxx)
}
render() => <xxx onClick={this.func1}></xxx>
}
「props」
组件内部不用声明props
,组件实例持有有一个props
数组
render(){
return (
<Com1 prop1='' prop2={666}/>
)
}
- 标签属性可以用展开运算符
- props 校验https://react.docschina.org/docs/typechecking-with-proptypes.html
Com1.propTypes = {
prop1:PropTypes.string.isRequired,// isRequired
prop2:PropTypes.number
prop3;PropTypes.func // function
}
Com1.defaultProps = {
prop1:'xxx',
prop2:123
}
- 同样也可以写在 class 内
class Com3 extends React.Component{
state = {xxx:xxx}
func1 = () => {
this.setState(xxx:xxx)
}
static propTypes = {
prop1:PropTypes.string.isRequired,// isRequired
prop2:PropTypes.number
prop3:PropTypes.func // function
}
static defaultProps = {
prop1:'xxx',
prop2:123
}
}
-
constructor
中要使用this.props
就必须接收并super
constructor(props){
super(props)
console.log(this.props);
}
「refs」
1.字符串形式( 过时 API )
<xxx ref="xxx">
2.回调函数
<xxx ref={c=>this.input1=c}>
// 就能挂载在 实例.input1 上
组件渲染时先会把 ref 清空(为 null),再赋值
使用 class 内绑定函数接收 node 可以避免上述
saveNode = c => this.input1 = c
render(){
return (
<input ref={this.saveNode}></input>
)
}
3.createRef
单一 ref 专用
input1 = React.createRef()
render(){
return (
<input ref={this.input1}></input>
)
}
// this.input1.current
React 生命周期
🚮 旧生命周期(<=16)
初始化阶段
-
constructor
-
componentWillMount
-
render
-
componentDidMount
更新
-
componentWillReceiveProps
-
shouldComponentUpdate
<– setState() -
componentWillUpdate
<—- forceUpdate() -
render
-
componentDidUpdate
销毁
-
componentWillUnmount
💡 新生命周期(>16)
示意图:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/
过时钩子
UNSAFE_componentWillMount
在此方法中同步调用 setState() 不会触发额外渲染
UNSAFE_componentWillReceiveProps
在组件接受新的 props 之前调用
UNSAFE_componentWillUpdate
初始化
// 1.
constructor(props)
// 2.不常用,仅适用于props派生state的情况,但会在每次render之前调用
static getDerivedStateFromProps(prop,state)
// 3.纯函数
render
// 4.组件挂载到DOM后
componentDidMount
更新
//1. 不常用
static getDerivedStateFromProps(prop,state)
//2.判断是否更新组件,
// 默认行为是true
// 当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用
// 使用forceUpdate可以跳过该方法
shouldComponentUpdate = ():boolean
//3.
render
//4.获取最近一次DOM更新前的DOM信息
getSnapshotBeforeUpdate = (prevProps,prevState):snapshot
//5.
componentDidUpdate(prevProps,preState,snapshot)
销毁
-
componentWillUnmount
代理
// /src/setupProxy.js
// CommonJS语法
const proxy = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
proxy("/api1", {
target: "http://xxxxxx",
changeOrigin: true, // Host字段
pathRewrite: { "/api1": "" },
})
);
};
组件间通信
Props
PubSubJS
发布订阅模式
https://github.com/mroderick/PubSubJS// create a function to subscribe to topics var mySubscriber = function (msg, data) { console.log(msg, data); }; // add the function to the list of subscribers for a particular topic // we're keeping the returned token, in order to be able to unsubscribe // from the topic later on var token = PubSub.subscribe("MY TOPIC", mySubscriber); // publish a topic asynchronously PubSub.publish("MY TOPIC", "hello world!"); // publish a topic synchronously, which is faster in some environments, // but will get confusing when one topic triggers new topics in the // same execution chain // USE WITH CAUTION, HERE BE DRAGONS!!! PubSub.publishSync("MY TOPIC", "hello world!");
Context
类似 vue 的 provide 和 inject
//祖先组件中 const MyContext1 = React.createContext(defaultValue); const MyContext2 = React.createContext(defaultValue); // defaultValue在不传value时生效 render(){ return ( // value应使用state属性避免意外的consumer更新 <Mycontext1.Provider value={}> <MyContext2.Provider value={}> ...<children /> </MyContext2.Provider> </Mycontext1.Provider> ) }
// 子组件中 // class static contextType = MyContext1 //只适用于消费一个context的情况 this.context//取值 // class&hooks <MyContext2.Consumer> { value=>( // render... ) } </MyContext2.Consumer>
Redux
路由
根路由标签
import { BrowserRouter } from "react-router-dom";
import { HashRouter } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App></App>
</BrowserRouter>
/* <HashRouter>
<App />
</HashRouter> */
</React.StrictMode>,
document.getElementById("root")
);
匹配规则
<link to="/xxx" />
<Route path="/xxx" component="{ComXXX1}">
<Route path="/xxx" component="{ComXXX2}"> <!-- ComXXX1 ComXXX2都渲染 --></Route></Route
>
Switch
<Switch>
/* Switch标签内只会渲染第一个匹配成功的组件 */
<Route exact path="/" component="{Home}" />
<Route path="/about" component="{About}" />
<Route path="/:user" component="{User}" />
<Route component="{NoMatch}" />
</Switch>
严格模式
<Route exact path="/xxx" component="{ComXXX1}"> <!-- 严格匹配 --></Route>
路由传参
match
- ⭐️
params
- (object) key/value 与动态路径的 URL 对应解析
- ⭐️
location
-
pathname
- (string 类型) URL 路径 - ⭐️
search
- (string 类型) URL 中的查询字符串 -
hash
- (string 类型) URL 的哈希片段 - ⭐️
state
- (object 类型) 提供给例如使用 push(path, state) 操将 location 放入堆栈时的特定 location 状态。只在浏览器和内存历史中可用。
-
-
history
URL 传 params
<Link to=`/xxx/${xxx.param1}`>
<Route path="/xxx/param1:param1"> // props.match.params // {param1:xxx}</Route>
URL 传 search
使用querrystring
处理 urlencode
<Link to=`/xxx?param1=${xxx.param1}¶m2=${xxx.param2}`>
<Route path="/xxx">
// props.location.search // "?param1=xxx¶m1=xxx" // qs.parse(props.location.search.slice(1)) => {...}</Route
>
传 state
<Link to={{pathname:'/xxx',state:{param1:xxx.param1}}}>
<Route path="/xxx"> // props.location.state</Route>
编程式路由
this.props.history
history
-
go
(n) - (function 类型) 将 history 堆栈中的指针调整 n -
goBack
() - (function 类型) 等同于 go(-1) -
goForward
() - (function 类型) 等同于 go(1) -
push
(path, [state]) - (function 类型) 在 history 堆栈添加个新目 -
replace
(path, [state]) - (function 类型) 替换在 history 堆中的
-
withRouter
可以通过 withRouter
高阶组件访问 history
对象的属性和最近的 <Route>
的 match
。 当路由渲染时, withRouter
会将已经更新的 match
, location
和 history
属性传递给被包裹的组件。
组件库
// ./config-overrides.js
const { override, fixBabelImports, addLessLoader } = require(`customize-cra`);
module.exports = override(
fixBabelImports("import", {
libraryName: "antd",
libraryDirectory: "es",
style: true,
})
);
Redux
redux
action
- 同步
action<object>
- 异步
action<function>
原生只能处理同步 action,action 中的 switch 机制
- 同步
-
reducer
store
-
dispatch
-
subscribe
-
react-redux
reducer
reducer 是一个纯函数:接收Action
和State
并作为参数,通过计算得到新的Store
纯函数:
- 不修改参数
- 不产生副作用
- 不调用不纯的方法
Math.random()``new Date()
const initState = 0; interface Action { type: string; data: number; } export default function countReducer(preState = initState, action: Action) { const { type, data } = action; switch (type) { case "increment": return preState + data; case "decrement": return preState - data; default: return preState; } }
connect API
使用哟 container 包裹 ui 组件,container 负责与 store 的交互,操作和数据通过 props 传给 ui 组件.
// container.ts import { connect } from "react-redux"; import xx组件 from "../XXX"; import { action1,action2 } from "../../redux/ACTIONS"; function mapStateToProps(state,ownProps) { // 将store中的state映射为props // ⭐️该函数必须是同步的 return { ... prop1:state.someState } } function mapDispatchToProps(dispatch){ // 映射dispatch为props return{ ... func1: () => dispatch({type:'someType'}) } } const CountContainer = connect(mapStateToProps,mapDispatchToProps)(xx组件); export default CountContainer;
多个reducer
// store.ts import { createStore, applyMiddleware, combineReducers } from "redux"; import countReducer from "./reducers/count"; import peopleReducer from "./reducers/peolple"; const allReducer = combineReducers({ count: countReducer, people: peopleReducer, }); export default createStore(allReducer);
异步 Action
通过 Redux 的中间件(middleware)实现异步Action
;
-
redux-thunk
等中间件可以让你提供一个拦截器在Reducer
处理Action
之前被调用; - 在这个拦截器中,你可以自由处理获得的 Action;
- 如果 Action 是个函数,
redux-thunk
将会尝试先执行函数而不是传给Reducer
,并dispatch
传给这个函数。
redux-devtools
yarn add redux-devtools-extension
// store.ts
import { composeWithDevtools} from 'redux-devtools-extension'
...
export default createStore(
allReducer, composeWithDevtools(applyMiddleware(thunk))
)
减少样板代码
redux-actions
https://github.com/redux-utilities/redux-actions
协助生成 action creatorimport { createAction } from 'redux-actions' ... export const increment = createAction('INCREMENT') import { handleActions } from 'redux-action' export default handleActions({ INCREMENT:state => state + 1 },0)
cli(yeoman)
其他
setState
对象式
setState(`stateChange(状态改变对象)`, [callBack]);
函数式
setState(`updater(返回stateChange对象的函数)`,[callback]) function updater(state,props){ ... 接收state和props参数 }
callback
在render()
后调用
路由懒加载
React.lazy
使用 dynamic import 标准,返回一个 promise,在 pending 状态期间渲染 loading 组件
import {lazy,Suspense} from 'react'
const Com1 = lazy(()=>{import('./Com1/index')})
render(){
return (
<NavLink to="/com1">Com1</NavLink>
<Suspense fallback={<Loading />}>
<Route path="/com1" component={Com1} />
</Suspense>
)
}
JSX Fragment
代替根 div
import { Fragment }> from 'react`
<Fragment key={}></Fragment>
<!-- 可以拥有key -->
<!-- 短语法 -->
<>
...
</>
子组件更新
一般子组件即使 props 没改变或者没接受 props 也会随父组件 rerender
PureComponent
**_浅层_**比较props
和state
,同时shouldComponentUpdate
将跳过所有子组件树的 prop
更新
组件 composition
组件可以接受任意 props,包括基本数据类型
将 jsx 元素以及函数通过 props 传递实现类似 vue slot 效果
向组件内部动态传入内容
.children
获得组件标签内内容
function FancyBorder(props) {
return <div className={"FancyBorder FancyBorder-" + props.color}>{props.children}</div>;
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">Welcome</h1>
<p className="Dialog-message">Thank you for visiting our spacecraft!</p>
</FancyBorder>
);
}
- 或者使用约定名称
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">{props.left}</div>
<div className="SplitPane-right">{props.right}</div>
</div>
);
}
function App() {
return <SplitPane left={<Contacts />} right={<Chat />} />;
}
- 通过 props 传递携带另一个 props 的函数
function App(){
return <A customizeFn={(p1)=>{<B p1={p1}/>}}/>
}
function A(){
const [state,changeState] = React.useState(123)
return (
<div>我是A组件</div>
{props.customizeFn(state)}
)
}
function B(props){
return (
<div>我是B我收到了{props.p1}</div>
)
}
错误边界
错误边界是一种 React 组件,这种组件可以捕获发生在其子组件树任何位置的 JavaScript 错误,并打印这些错误,同时展示降级 UI,而并不会渲染那些发生崩溃的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
logErrorToMyService(error, errorInfo);
}
面试题
React的性能优化
useCallback
管理函数的再执行useMemo
管理值的计算PureComponent
浅层比较state
和prop
不重复渲染React.memo
浅层比较prop
lazy
+Suspense
import {lazy,Suspense} from 'react' const lazyComponent = () => { const Com = lazy(()=>import(/* webpackChunkName:" 自定义chunk名" */'./xxx.js')) return ( <Suspense fallback={<div>loading...</div>}> <Com> </Suspense> ) }
- 使用
Fragment
标签<></>
- 不要使用内联函数
<input onClick={()=>{console.log(666)}}></input>
- 注意
Context provide
触发的渲染无法避免 - 类组件中不使用箭头函数绑定
this
- 避免使用内联样式
style = {}
- 设定错误边界
- 避免数据突变