组件
-
组件必须以大写字母开头。
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
。比如useState
、useEffect
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-types
:npm 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
会对props
和state
进行浅层比较,并减少了跳过必要更新的可能性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
脚手架
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
- 一个轻量级的应用框架
- 一个基于
redux
和redux-saga
的数据流方案 - 额外内置了
react-router
和fetch
- 基于
roadhog
常用命令
npm i dva-cli -g
:全局安装dva -v
:查看版本号dva new projectname
:新建项目
参考链接
umi
- 可扩展的企业级前端应用框架
- 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展
- 配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求
- 集合了
roadhog
+ 路由 +html
生成 + 完善的插件机制
参考链接
发表评论