一. react基础知识部分
我们在使用react开发网页时,会下载两个包,一个是react,一个是react-dom,其中:
react包是react的核心代码,
react-dom则是React剥离出的涉及DOM操作的部分
最简单的HelloWorld程序如下:
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
1.1 重要概念
1.1.1 JSX
JavaScript语法糖,可以很好地描述 UI 应该呈现出它应有交互的本质形式。
Babel 会把 JSX 转译成一个名为 React.createElement()
函数调用。
以下代码等效:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 等效
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
JSX支持DOM标签和用户自定义的组件, 小写字母开头视为原生DOM标签,大小字母开头视为用户自定义组件
1.1.2 函数组件与class组件以及Props
定义组件最简单的方式就是编写 JavaScript 函数
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
也可以使用ES6的class来定义组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
1.1.3 State
State用来存储组件内可变的数据,不要直接修改State,要通过setState函数来进行修改
this.setState({comment: 'Hello'});
异步更新
出于性能考虑,React 可能会把多个 setState()
调用合并成一个调用。因为 this.props
和 this.state
可能会异步更新,所以你不要依赖他们的值来更新下一个状态。如果要依赖this.props更新下一个状态可以传递给setState函数一个回调函数
this.setState((state, props) => ({
counter: state.counter + props.increment // 这里的props为更新被应用时的最新值
}));
更新合并
当你调用 setState()
的时候,React 会把你提供的对象合并到当前的 state,这里的合并是浅合并
数据单向流动,自顶向下
组件可以选择把它的 state 作为 props 向下传递到它的子组件中:
<FormattedDate date={this.state.date} />
任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。
1.1.4 事件处理
- 事件名小驼峰形式: 如onClick
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
- 不能通过返回
false
的方式阻止默认行为。你必须显式的使用preventDefault
function ActionLink() {
function handleClick(e) {
/*
e是一个合成事件(SyntheticEvent),并不是原生事件
*/
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
-
this的处理
在 JavaScript 中,class 的方法默认不会绑定this
。如果你忘记绑定this.handleClick
并把它传入了onClick
,当你调用这个函数的时候this
的值为undefined
三种解决方法:-
constructor(props) { super(props); this.state = {isToggleOn: true}; // 为了在回调中使用 `this`,这个绑定是必不可少的 this.handleClick = this.handleClick.bind(this); }
-
handleClick = () => { console.log('this is:', this); }
-
<button onClick={() => this.handleClick()}> Click me </button>
-
-
向事件处理函数传递参数
2中方式:-
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
-
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
在这两种情况下,React 的事件对象
e
会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过bind
的方式,事件对象以及更多的参数将会被隐式的进行传递 -
1.1.5 条件渲染
-
使用if/else
function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn) { return <UserGreeting />; } return <GuestGreeting />; }
-
使用元素变量
render() { const isLoggedIn = this.state.isLoggedIn; let button; if (isLoggedIn) { button = <LogoutButton onClick={this.handleLogoutClick} />; } else { button = <LoginButton onClick={this.handleLoginClick} />; } return ( <div> <Greeting isLoggedIn={isLoggedIn} /> {button} </div> ); }
-
&& 运算符, 三目运算符的使用
通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符和三目运算符function Mailbox(props) { const unreadMessages = props.unreadMessages; return ( <div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> You have {unreadMessages.length} unread messages. </h2> } </div> ); } // 三目运算符渲染不同的用户自定义组件 render() { const isLoggedIn = this.state.isLoggedIn; return ( <div> {isLoggedIn ? <LogoutButton onClick={this.handleLogoutClick} /> : <LoginButton onClick={this.handleLoginClick} /> } </div> ); }
-
阻止组件渲染
让render
方法直接返回null
1.1.6 列表和key
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number,index) =>
// key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。应该是在列表元素属性中独一无二的值,实在找不到可以用index
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
// 元素的 key 只有放在就近的数组上下文中才有意义。
比方说,如果你提取出一个 ListItem 组件,你应该把 key 保留在数组中的这个 <ListItem /> 元素上,而不是放在 ListItem 组件中的 <li> 元素上。
// 也可以直接将map直接嵌入到JSX中
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
1.1.7 表单
受控组件
html中, input等表单元素维护自己的值,并根据用户输入进行更新,在React中可变状态通常用state来存储,并通过setState函数来进行更新,我们结合它们使得state成为表单唯一数据源,并且在用户输入过程中及时更新state的值, 以这种方式来控制取值的表单输入元素就叫做**受控组件 **
class NameForm extends React.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>
);
}
}
非受控组件
受控组件每个状态更新都需要编写数据处理函数,非常麻烦,可以使用ref来直接从DOM节点中获取表单数据
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
默认值
在 React 渲染生命周期时,表单元素上的 value
将会覆盖 DOM 节点中的值,在非受控组件中,你经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新。 在这种情况下, 你可以指定一个 defaultValue
属性,而不是 value
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
// 同样,<input type="checkbox"> 和 <input type="radio"> 支持 defaultChecked,<select> 和 <textarea> 支持 defaultValue。
文件上传
在 React 中,<input type="file" />
始终是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。
应该使用File API与文件进行交互
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
1.1.8 状态提升
React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。
1.1.9 代码分割
-
使用ES6 import(当使用 Babel 时,你要确保 Babel 能够解析动态 import 语法而不是将其进行转换。对于这一要求你需要 babel-plugin-syntax-dynamic-import 插件)
-
React.lazy和Suspense(不支持SSR)
import React, { Suspense } from 'react'; const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </Suspense> </div> ); }
-
基于路由的代码分割
import React, { Suspense, lazy } from 'react'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; const Home = lazy(() => import('./routes/Home')); const About = lazy(() => import('./routes/About')); const App = () => ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> </Switch> </Suspense> </Router> );
1.1.10 Context
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,通过这种方式不用显示地通过组件树逐层传递props, 而是可以将数据向组件树下所有的组件进行广播
-
使用Context需要谨慎, 因为这会使得组件复用性变差
-
使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据
-
如果只是想避免层层传递一些属性,应该使用组件组合(component composition)
即将组件作为属性进行传递,这样只有最顶层的组件需要知道某些属性的使用用途const { PureComponent, Fragment } = require("react"); function PageLayout(props){ return ( <Fragment> <header> {props.header} </header> <main> {props.main} </main> <footer> {props.footer} </footer> </Fragment> ); } function Page(props){ return ( <PageLayout header={<h1>this is header</h1>} main={<p>this is main text</p>} footer={<h3>this is footer</h3>}/> ); } class PageComponent extends PureComponent { render(){ return ( <Fragment> <h1>Home Page</h1> <Page /> </Fragment> ); } } export default PageComponent;
-
主要API
-
React.createContext
用来创建一个Context对象,在组件树中能够对其包裹的组件提供值, 只有当组件所处的树中没有匹配到Provider时候, Context对象的defaultValue才会生效 -
Context.Provider
每个Context对象都会返回一个Provider React组件, 它允许消费组件订阅context的变化, Provider接收一个value属性,传递给消费组件,一个Provider可以和多个消费组件有对应关系,多个Provider也可以嵌套使用,里层的会覆盖外层的数据, 当Provider的值发生变化时候,它内部的所有消费组件都会重新渲染(不受shouldComponentUpdate函数影响) -
Class.contextType
挂载在 class 上的contextType
属性会被重赋值为一个由React.createContext()
创建的 Context 对象。这能让你使用this.context
来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中。 -
Context.Consumer
<MyContext.Consumer> {value => /* 基于 context 值进行渲染*/} </MyContext.Consumer> // 这能在函数式组件中完成context的订阅
-
Context.displayName
context 对象接受一个名为displayName
的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容 -
在嵌套组件中更新Context
可以通过 context 传递一个函数,使得 consumers 组件更新 context// context.js export const ThemeContext = React.createContext({ theme: 'light', toggleTheme: () => {}, }); import { PureComponent } from "react"; import loadable from "@loadable/component"; import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import { ThemeContext } from "./context"; const Login = loadable(() => import("./Login")); const Home = loadable(() => import("./Home")); // App.js class App extends PureComponent { constructor(props) { super(props); this.toggleTheme = () => { this.setState(state => ({ themeName: state.themeName === 'light'? 'dark' : 'light' })) } this.state = { themeName: 'light', toggleTheme: this.toggleTheme }; } render() { return ( <ThemeContext.Provider value={this.state}> <Router> <Switch> <Route exact path="/" component={Login} /> <Route path="/home" component={Home} /> </Switch> </Router> </ThemeContext.Provider> ); } } export default App; //login.js import { ThemeContext } from './context'; const { PureComponent, Fragment } = require("react"); class Login extends PureComponent { static contextType = ThemeContext; componentDidMount(){ console.log(this.context) } toggleTheme = () => { this.context.toggleTheme(); } render(){ return ( <Fragment> <h1 style={{color: this.context.themeName === 'dark'? '#000': '#ddd'}}>Login Page</h1> <ThemeContext.Consumer> { ({themeName, toggleTheme}) => <div> <h3>目前的主题是: {themeName}</h3> <button onClick={toggleTheme}>login页面切换主题</button> </div> } </ThemeContext.Consumer> </Fragment> ); } } export default Login;
优化
以下的代码会重渲染所有下面的 consumers 组件,因为
value
属性总是被赋值为新的对象:class App extends React.Component { render() { return ( <MyContext.Provider value={{something: 'something'}}> <Toolbar /> </MyContext.Provider> ); } } // 为了防止这种情况,将 value 状态提升到父节点的 state 里: class App extends React.Component { constructor(props) { super(props); this.state = { value: {something: 'something'}, }; } render() { return ( <Provider value={this.state.value}> <Toolbar /> </Provider> ); } }
-
1.1.11 错误边界(Error Boundaries)
- 部分UI的JavaScript错误不应该导致整个应用崩溃
- 错误边界是React中的一种组件,可以捕获并打印发生在其子组件树任何位置的JavaScript错误,并且,它会渲染出备用UI
- 错误边界无法捕获错误的情况: 事件处理(需要使用try {} catch(e){}), 异步代码, 服务端渲染,它自身产生的错误
错误边界的工作方式类似于 JavaScript 的 catch {}
,不同的地方在于错误边界只针对 React 组件。只有 class 组件才可以成为错误边界组件大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。
import { PureComponent } from 'react';
/**
如果一个 class 组件中定义了 static getDerivedStateFromError() 或 componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用 static getDerivedStateFromError() 渲染备用 UI ,使用 componentDidCatch() 打印错误信息。
**/
class ErrorBoundary extends PureComponent {
constructor(props){
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error(error, errorInfo);
}
render(){
if(this.state.hasError){
return <h1>Something went wrong</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
注意
开发模式下页面会崩溃提醒我们异常出现,生产模式下错误会被错误边界捕获, 在控制台进行打印, 但是保证系统不因为局部错误而崩溃。
1.1.12 refs转发
refs转发是将某些组件赋予ref, 并将此引用传递到其子组件中
转发refs到DOM组件
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;
/**
我们通过调用 React.createRef 创建了一个 React ref 并将其赋值给 ref 变量。
我们通过指定 ref 为 JSX 属性,将其向下传递给 <FancyButton ref={ref}>。
React 传递 ref 给 forwardRef 内函数 (props, ref) => ...,作为其第二个参数。
我们向下转发该 ref 参数到 <button ref={ref}>,将其指定为 JSX 属性。
当 ref 挂载完成,ref.current 将指向 <button> DOM 节点。
**/
/**
第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。
Ref 转发不仅限于 DOM 组件,你也可以转发 refs 到 class 组件实例中。
**/
在高阶组件HOC中转发refs
下面是一个HOC的实例
function logProps(WrappedComponent) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
return LogProps;
// ref因为不是props因此不会透传(pass through)到被包裹的组件
// 要像下面这样处理
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
// 然后它就可以被挂载到被 LogProps 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
1.1.13 Fragments
用于在不额外增加DOM元素的前提下载一个组件中返回多个元素
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
简写
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
将一个集合映射到一个 Fragments 数组
<dl>
{props.items.map(item => (
// 没有`key`,React 会发出一个关键警告
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
1.1.14 高阶组件HOC
是一种React中的组件复用高级技巧, 高阶组件是参数为组件,返回值为新组件的函数
HOC 在 React 的第三方库中很常见,例如 Redux 的 connect
和 Relay 的 createFragmentContainer
。
HOC是用来解决mixins 引入隐式依赖关系导致依赖关系不明确, mixin命名冲突, 滚雪球的复杂性问题
下面是一个简单的HOC例子:
function logProps(WrappedComponent) {
return class extends React.Component {
componentDidUpdate(prevProps) {
console.log('Current props: ', this.props);
console.log('Previous props: ', prevProps);
}
render() {
// 将 input 组件包装在容器中,而不对其进行修改。Good!
return <WrappedComponent {...this.props} />;
}
}
}
1.1.15 深入JSX
JSX 仅仅只是 React.createElement(component, props, ...children)
函数的语法糖
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
会编译为:
React.createElement(
MyButton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
运行时动态决定渲染组件
import React from 'react';
import { PhotoStory, VideoStory } from './stories';
const components = {
photo: PhotoStory,
video: VideoStory
};
function Story(props) {
// 正确!JSX 类型可以是大写字母开头的变量。
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}
1.1.16 性能优化(Optimizing Performance)
-
上线后要使用生产版本react.js
-
如果你知道在什么情况下你的组件不需要更新,你可以在
shouldComponentUpdate
中返回false
来跳过整个渲染过程。其包括该组件的render
调用以及之后的操作 -
在大部分情况下,你可以继承
React.PureComponent
以代替手写shouldComponentUpdate()
。 -
PureComponent只会进行浅比较, 因此如果是渲染的数组的话, 向数组中push元素, 浅比较并不能保证正确更新
解决这个问题使用不可变数据, 如concat, ES6展开运算符以生成新的数组引用
推荐使用immutable.js
1.1.17 Portals
是一种将子节点渲染到存在于父组件以外的DOM节点的方案
ReactDOM.createPortal(child, container)
第一个参数(child
)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container
)是一个 DOM 元素。
一个 portal 的典型用例是当父组件有 overflow: hidden
或 z-index
样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框:
一个从 portal 内部触发的事件会一直冒泡至包含 React 树的祖先,即便这些元素并不是 DOM 树 中的祖先
// 在 DOM 中有两个容器是兄弟级 (siblings)
const appRoot = document.getElementById('app-root');
const modalRoot = document.getElementById('modal-root');
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement('div');
}
componentDidMount() {
// 在 Modal 的所有子元素被挂载后,
// 这个 portal 元素会被嵌入到 DOM 树中,
// 这意味着子元素将被挂载到一个分离的 DOM 节点中。
// 如果要求子组件在挂载时可以立刻接入 DOM 树,
// 例如衡量一个 DOM 节点,
// 或者在后代节点中使用 ‘autoFocus’,
// 则需添加 state 到 Modal 中,
// 仅当 Modal 被插入 DOM 树中才能渲染子元素。
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(
this.props.children,
this.el
);
}
}
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {clicks: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// 当子元素里的按钮被点击时,
// 这个将会被触发更新父元素的 state,
// 即使这个按钮在 DOM 中不是直接关联的后代
this.setState(state => ({
clicks: state.clicks + 1
}));
}
render() {
return (
<div onClick={this.handleClick}>
<p>Number of clicks: {this.state.clicks}</p>
<p>
Open up the browser DevTools
to observe that the button
is not a child of the div
with the onClick handler.
</p>
<Modal>
<Child />
</Modal>
</div>
);
}
}
function Child() {
// 这个按钮的点击事件会冒泡到父元素
// 因为这里没有定义 'onClick' 属性
return (
<div className="modal">
<button>Click</button>
</div>
);
}
ReactDOM.render(<Parent />, appRoot);
1.1.18 Profiler
Profiler
能添加在 React 树中的任何地方来测量树中这部分渲染所带来的开销。 它需要两个 prop :一个是 id
(string),一个是当组件树中的组件“提交”更新的时候被React调用的回调函数 onRender
(function)。
render(
<App>
<Profiler id="Navigation" onRender={callback}>
<Navigation {...props} />
</Profiler>
<Main {...props} />
</App>
);
Profiler必须指定id prop, 可以嵌套测试组件树种不同部分的性能开销
Profiler
需要一个 onRender
函数作为参数。 React 会在 profile 包含的组件树中任何组件 “提交” 一个更新的时候调用这个函数。 它的参数描述了渲染了什么和花费了多久。
function onRenderCallback(
id, // 发生提交的 Profiler 树的 “id”
phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
) {
// 合计或记录渲染时间。。。
}
1.1.19静态类型检查
小型项目使用prop-types
大型项目使用Flow或者TypeScript
import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为 JS 原生类型,默认情况下
// 这些属性都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、元素或数组)
// (或 Fragment) 也包含这些类型。
optionalNode: PropTypes.node,
// 一个 React 元素。
optionalElement: PropTypes.element,
// 一个 React 元素类型(即,MyComponent)。
optionalElementType: PropTypes.elementType,
// 你也可以声明 prop 为类的实例,这里使用
// JS 的 instanceof 操作符。
optionalMessage: PropTypes.instanceOf(Message),
// 你可以让你的 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
}),
// An object with warnings on extra properties
optionalObjectWithStrictShape: PropTypes.exact({
name: PropTypes.string,
quantity: PropTypes.number
}),
// 你可以在任何 PropTypes 属性后面加上 `isRequired` ,确保
// 这个 prop 没有被提供时,会打印警告信息。
requiredFunc: PropTypes.func.isRequired,
// 任意类型的数据
requiredAny: PropTypes.any.isRequired,
// 你可以指定一个自定义验证器。它在验证失败时应返回一个 Error 对象。
// 请不要使用 `console.warn` 或抛出异常,因为这在 `onOfType` 中不会起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 你也可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器。
// 它应该在验证失败时返回一个 Error 对象。
// 验证器将验证数组或对象中的每个值。验证器的前两个参数
// 第一个是数组或对象本身
// 第二个是他们当前的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};
默认Prop值
// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};
如果你正在使用像 transform-class-properties 的 Babel 转换工具,你也可以在 React 组件类中声明 defaultProps
作为静态属性
1.1.20 Hook
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
使用State Hook
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useState参数为初始state值, 返回值为当前 state 以及更新 state 的函数
使用Effect Hook
Effect Hook 可以让你在函数组件中执行副作用(数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用)操作
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => { document.title = `You clicked ${count} times`; });
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行,
你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect
的第二个可选参数即可:
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
Hook规则
- 只在最顶层使用Hook, 不要在循环,条件或者嵌套函数中调用Hook
- 只在React函数中调用Hook, 不要在普通的JavaScript函数中调用Hook
自定义Hook
自定义 Hook 是一个函数,其名称以 必须以“use
” 开头(React才能检查其内部是否包含对内部Hook的调用和对是否违反Hook规则进行检查),函数内部可以调用其他的 Hook
下面是一个订阅好友在线状态的自定义Hook
import { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
使用上面的自定义Hook
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
自定义Hook是一种重用状态逻辑的机制,因此每次使用自定义Hook时,其中所有的state和副作用都是完全隔离的。
React内部提供的一些自定义Hook