React笔记

组件

  • 组件必须以大写字母开头。React会将以小写字母开头的组件视为原生DOM标签。<Welcome />

  • 有状态组件和无状态组件的本质区别就是:有无state属性和有无生命周期函数

class创建的组件叫做有状态组件【用的最多】

  • 有自己的私有数据(this.state)和生命周期函数
  • 如果一个组件需要有自己的私有数据,则推荐使用class创建的有状态组件

function创建的组件叫做无状态组件【无状态组件用的不多】

  • 只有props,没有自己的私有数据和生命周期函数
  • 如果一个组件不需要私有的数据,则推荐使用,无状态组件
  • 无状态组件,由于没有自己的state和生命周期函数,所以运行效率会比又状态组件稍微高一些

函数组件

function Adder(props) { // 无状态组件没有自己的state,不要这么用 // 如果想使用state的话,可以用 useState let say = 'hello' return ( <div className="parent"> <p>My name is {props.name}, age: {props.age}, say: {say}</p> </div> ) } function App() { return ( <div> <Addr name="Addr1" age="12" /> </div> ) }
  • 如果要使用生命周期等,需要用到Hooks。比如useStateuseEffect

class组件

  • 有自己的私有数据(this.state)和生命周期函数
import React, { Component } from 'react' class Addr1 extends Component { constructor(props) { super(props) this.state = { number: 0, firstName: 'Wang', lastName: 'lifa' } } // 直接在属性前面写一个get 就是获取当前属性,也就是计算属性 get name() { return this.state.firstName + this.state.lastName } add() { // this.setState({ // number: this.state.number + 1 // }) // 可以用回调的形式进行多次更新 this.setState(state => { return { number: state.number + 1 } }) } minus() { this.setState({ number: this.state.number - 1 }) } render() { return ( <div className="red"> <span>{this.state.number}</span> <button onClick={this.add.bind(this)}>+1</button> <button onClick={this.minus.bind(this)}>-1</button> {this.name} </div> ) } }
  • 必须要继承React.Component
  • constructor里要传入props,并调用super(props),因为这是ES6语法的规定
  • this.state用来存放自身属性,但是修改的时候要用this.setState(newState),而不能直接this.state.number += 1
  • 绑定方法的时候要绑定this,因为React会这样调用this.add.call(undefined, ...)

state

class Test extends React.Component { constructor(props) { super(props) // 不要在这里调用 this.setState() this.state = { counter: 0 } } }

setState

语法一:setState(updater, [callback])

第一个参数为带有形式参数的 updater 函数:(state, props) => stateChange

// 根据 props.step 来增加 state this.setState((state, props) => { return { counter: state.counter + props.step } })

第二个参数为可选的回调函数,它将在 setState 完成合并并重新渲染组件后执行。通常,我们建议使用 componentDidUpdate() 来代替此方式。

语法二:setState(stateChange[, callback])

第一个参数可以接受对象类型。stateChange会将传入的对象浅层合并到新的 state

this.setState({ quantity: 2 })

Props

PropTypes

组件属性类 propTypes 用来验证组件实例的属性是否符合要求

在多人开发时,当被人使用自己定义的组件时,有可能出现类型传错的情况,而在自己的组件上加上prop-types,他可以对父组件传来的props进行检查

组件的属性可以接收任意的值,数字,函数,字符串等

// 可以声明 prop 为指定的 JS 基本类型。默认 // 情况下没有isRequired,这些 prop 都是可传可不传的。 optionalArray: PropTypes.array, optionalBool: PropTypes.bool, optionalFunc: PropTypes.func, optionalNumber: PropTypes.number, optionalObject: PropTypes.object, optionalString: PropTypes.string, optionalSymbol: PropTypes.symbol, // 所有可以被渲染的对象:数字, // 字符串,DOM 元素或包含这些类型的数组。 optionalNode: PropTypes.node, // React 元素 optionalElement: PropTypes.element, // 用 JS 的 instanceof 操作符声明 prop 为类的实例。 optionalMessage: PropTypes.instanceOf(Message), // 用 enum 来限制 prop 只接受指定的值。 optionalEnum: PropTypes.oneOf(['News', 'Photos']), // 指定的多个对象类型中的一个 optionalUnion: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, PropTypes.instanceOf(Message) ]), // 指定类型组成的数组 optionalArrayOf: PropTypes.arrayOf(PropTypes.number), // 指定类型的属性构成的对象 optionalObjectOf: PropTypes.objectOf(PropTypes.number), // 特定形状参数的对象 optionalObjectWithShape: PropTypes.shape({ color: PropTypes.string, fontSize: PropTypes.number }), // 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接 // 使用 `console.warn` 或抛异常,因为这样 `oneOfType` 会失效。 customProp: function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error('Validation failed!'); } }
  • 安装prop-typesnpm i prop-types --save

  • 使用示例 - class组件

// ... import PropTypes from 'prop-types' // ... export default class Rank extends Component { // 如果没有传递该属性时的默认值 static defaultProps = { list: [] } // 属性限制(类型,是否必传等) static propTypes = { list: PropTypes.array } // ... }
  • 使用示例 - 函数组件
import PropTypes from 'prop-types' function ListPk(props) { // ... return ( // ... ) } // 默认值 ListPk.defaultProps = { list: [] } // 属性限制 ListPk.propTypes = { list: PropTypes.array } export default ListPk
  • 多重嵌套类型检测
// An array of a certain type // optionalArrayOf: PropTypes.arrayOf(PropTypes.number), // An object with property values of a certain type // optionalObjectOf: PropTypes.objectOf(PropTypes.number), export default class Test extends Component { static propTypes = { todoList:PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, text: PropTypes.string })) } // static propTypes = { // object:PropTypes.shape({ // name:PropTypes.string, // age:PropTypes.number // }) // } }

生命周期

生命周期

挂载阶段:

  • constructor(props):如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数
  • static getDerivedStateFromProps(props, state):在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
  • render()class组件中必须实现的方法。如果 shouldComponentUpdate()返回 false,则不会调用 render()
  • componentDidMount():会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
  • UNSAFE_componentWillMount():即将过时
    更新阶段:
  • static getDerivedStateFromProps(props, state)
  • shouldComponentUpdate(nextProps, nextState):在这里优化,是否需要更新。若返回false将不会调用render()componentDidUpdate()。首次渲染或使用 forceUpdate() 时不会调用该方法。PureComponent会对 propsstate 进行浅层比较,并减少了跳过必要更新的可能性
  • render()class组件中必须实现的方法。如果 shouldComponentUpdate()返回 false,则不会调用 render()
  • getSnapshotBeforeUpdate(prevProps, prevState):如果实现了该生命周期,则它的返回值将作为componentDidUpdate()的第三个参数snapshot传递,否则此参数为undefined
  • componentDidUpdate(prevProps, prevState, snapshot):会在更新后会被立即调用。首次渲染不会执行此方法。
  • UNSAFE_componentWillUpdate():即将过时
  • UNSAFE_componentWillReceiveProps():即将过时
    卸载阶段:
  • componentWillUnmount:会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。这里面不应该调用 setState()
    错误处理:
  • static getDerivedStateFromError(error):在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state
  • componentDidCatch(error, info)
class Test extends React.Component { constructor(props) { super(props) // 不要在这里调用 this.setState() this.state = { counter: 0 } } componentDidUpdate(prevProps) { // 典型用法(不要忘记比较 props) if (this.props.userID !== prevProps.userID) { this.fetchData(this.props.userID) } } }

事件

  • React事件的命名采用小驼峰式(camelCase),而不是纯小写
  • 使用JSX语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
  • 不能通过return false的方式阻止默认行为。你必须显式的使用e.preventDefault

函数组件

function ActionLink() { function handleClick(e) { e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> ); }
  • 带参数的
function Example() { const [count, setCount] = useState(0) return ( <div> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ) }

class组件

  • 方法一:在render中绑定。每次render都会重新绑定,浪费性能
// ... class Toggle extends Component { handleClick() { // ... } render() { return ( <button onClick={this.handleClick.bind(this)}> {/* ... */} </button> ) } }
  • 方法二:使用箭头函数,同理,也是浪费性能(每次渲染,回调函数都是不同的匿名函数)
class Toggle extends Component { handleClick() { // ... } render() { return ( <button onClick={ (e) => this.handleClick(e))}> {/* ... */} </button> ) } }
  • 方法三:在构造函数中绑定,推荐做法
// ... class Toggle extends Component { constructor(props) { super(props) this.state = { isToggleOn: true } // 为了在回调中使用 `this`,这个绑定是必不可少的 this.handleClick = this.handleClick.bind(this) } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })) } render() { return ( <button onClick={this.handleClick}> { this.state.isToggleOn ? 'ON' : 'OFF' } </button> ) } } // 如果有很多函数需要绑定,可以这么写 class Toggle1 extends Component { constructor(props) { super(props) // ... // 为了在回调中使用 `this`,这个绑定是必不可少的 this.bind([ 'eventHandler1', 'eventHandler2', 'eventHandler3', 'eventHandler4', 'eventHandler5', ]) } bind(methodArray) { methodArray.forEach(method => this[method] = this[method].bind(this)) } }
  • 方法四:使用class properties进行绑定。目前处于草案阶段,babel已经支持
// ... class Toggle extends Component { constructor(props) { super(props) this.state = { isToggleOn: true } } handleClick = () => { this.setState(state => ({ isToggleOn: !state.isToggleOn })) } render() { return ( <button onClick={this.handleClick}> { this.state.isToggleOn ? 'ON' : 'OFF' } </button> ) } }

备注

  • 推荐使用第三种方法

需要参数传递时,可以有以下几种写法

  • 方法一:在render中绑定
class Toggle extends Component { handleClick(id) { // ... } render() { return ( <button onClick={this.handleClick.bind(this, id)}> {/* ... */} </button> ) } }
  • 方法二:使用箭头函数
class Toggle extends Component { handleClick(id) { // ... } render() { return ( <button onClick={ (e) => this.handleClick(id, e)}> {/* ... */} </button> ) } }
  • 方法三:使用e.target.dataset
class Toggle extends Component { constructor(props) { super(props) this.handleClick = this.handleClick.bind(this) } handleClick(e) { const { id } = e.target.dataset // ... } render() { return ( <button data-id={id} onClick={this.handleClick}> {/* ... */} </button> ) } }

渲染

// if if (isLoggedIn) { return <UserGreeting />; } return <GuestGreeting />; // if if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } // && {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } // condition ? true : false <div> The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in. </div> // condition ? true : false <div> {isLoggedIn ? ( <LogoutButton onClick={this.handleLogoutClick} /> ) : ( <LoginButton onClick={this.handleLoginClick} /> )} </div> // map const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li key={number.toString()}>{number}</li> ); // map <ul> {numbers.map((number) => <ListItem key={number.toString()} value={number} /> )} </ul>

表单

  • 单个输入
import React, { Component } from 'react' class NameForm extends Component { constructor(props) { super(props); this.state = {value: ''}; this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { this.setState({value: event.target.value}); } handleSubmit(event) { alert('提交的名字: ' + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> 名字: <input type="text" value={this.state.value} onChange={this.handleChange} /> </label> <input type="submit" value="提交" /> </form> ); } }
  • 多个输入
class Reservation extends React.Component { constructor(props) { super(props); this.state = { isGoing: true, numberOfGuests: 2 }; this.handleInputChange = this.handleInputChange.bind(this); } handleInputChange(event) { const target = event.target; const value = target.type === 'checkbox' ? target.checked : target.value; const name = target.name; this.setState({ [name]: value }); } render() { return ( <form> <label> 参与: <input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} /> </label> <br /> <label> 来宾人数: <input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} /> </label> </form> ); } }

Fragments

import React, { Component, Fragment } from 'react' class Columns extends React.Component { render() { return ( <Fragment> <td>Hello</td> <td>World</td> </Fragment> ); } } // 使用短语法 // class Columns extends React.Component { // render() { // return ( // <> // <td>Hello</td> // <td>World</td> // </> // ); // } // }

Suspense

用于异步加载

import React, { lazy, Suspense } from 'react' // import SvgIcon from '@/components/SvgIcon' // 使用React.lazy异步加载组件 const SvgIcon = lazy(() => import(/* webpackChunkName: 'SvgIcon' */ '@/components/SvgIcon') ) export default function TestPage(props) { return ( <div> {/* <SvgIcon iconClass="qq" className="test" /> */} {/* fallback也可以是import引入的组件或者null */} <Suspense fallback={<div>loading</div>}> <SvgIcon iconClass="qq" /> </Suspense> </div> ) }

Hooks

参考:React hooks笔记

脚手架

create-react-app

  • 官方提供的,不可配置,支持typescript
  • 路由、数据管理、接口请求等需要自己安装配置

常用命令

  • npm i create-react-app -g:全局安装脚手架
  • create-react-app -V:查看版本号
  • create-react-app projectname:新建项目
  • create-react-app projectname --typescript:新建typescript项目
  • npm run eject:弹出该项目的配置(单向,不可恢复)

参考链接

roadhog

  • 基于webpack的封装工具,目的是简化webpack的配置
  • 可配置版的create-react-app,可定制配置
  • 提供mock功能
  • 提供了JSON格式的配置方式

常用命令

  • npm i roadhog -g:全局安装
  • roadhog server:本地开发
  • roadhog build:打包发布
  • roadhog test:测试,默认会跑 ./test 目录下的所有文件

dva

  • 一个轻量级的应用框架
  • 一个基于 reduxredux-saga 的数据流方案
  • 额外内置了 react-routerfetch
  • 基于roadhog

常用命令

  • npm i dva-cli -g:全局安装
  • dva -v:查看版本号
  • dva new projectname:新建项目

参考链接

umi

  • 可扩展的企业级前端应用框架
  • 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展
  • 配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求
  • 集合了roadhog + 路由 + html生成 + 完善的插件机制

参考链接


创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: