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}/>
  )
}
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": "" },
		})
	);
};

组件间通信

  1. Props

  2. 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!");
  3. 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>
  4. 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>

使用querrystring处理 urlencode

<Link to=`/xxx?param1=${xxx.param1}&param2=${xxx.param2}`>

<Route path="/xxx">
	// props.location.search // "?param1=xxx&param1=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 会将已经更新的 matchlocationhistory 属性传递给被包裹的组件。

组件库

// ./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 是一个纯函数:接收 ActionState 并作为参数,通过计算得到新的Store

    纯函数:

    1. 不修改参数
    2. 不产生副作用
    3. 不调用不纯的方法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 creator

    import { 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参数
    }
  • callbackrender()后调用

路由懒加载

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**_浅层_**比较propsstate,同时shouldComponentUpdate 将跳过所有子组件树的 prop 更新

组件 composition

组件可以接受任意 props,包括基本数据类型

将 jsx 元素以及函数通过 props 传递实现类似 vue slot 效果

向组件内部动态传入内容

  1. .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>
	);
}
  1. 或者使用约定名称
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 />} />;
}
  1. 通过 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的性能优化

  1. useCallback管理函数的再执行
  2. useMemo管理值的计算
  3. PureComponent浅层比较stateprop不重复渲染
  4. React.memo浅层比较prop
  5. 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>
      )
    }
  6. 使用Fragment标签<></>
  7. 不要使用内联函数
    <input onClick={()=>{console.log(666)}}></input>
  8. 注意Context provide触发的渲染无法避免
  9. 类组件中不使用箭头函数绑定this
  10. 避免使用内联样式style = {}
  11. 设定错误边界
  12. 避免数据突变

react-beginner
http://yoursite.com/2022/02/24/react_rookie/
作者
tatekii
发布于
2022年2月24日
许可协议