安装
npx create-react-app my-app
cd my-app
npm start
核心概念
Hello world
import React from "react";
import ReactDOM from "react-dom";
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
JSX简介
-
可以内嵌参数
const name = "Josh Perez"; const element = <h1>Hello, {name}</h1>; function formatName(user) { return user.firstName + ' ' + user.lastName; } const element = <h1>Hello, {formatName(user)}!</h1>; -
JSX表达式
function getGreeting(user) { if (user) { return <h1>Hello, {formatName(user)}!</h1>; } return <h1>Hello, Stranger.</h1>; } -
JSX特定属性
const element = <img src={user.avatarUrl}></img>; -
JSX 指定子元素
const element = ( <div> <h1>Hello!</h1> <h2>Good to see you here.</h2> </div> );
元素渲染
ReactDOM.render()
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
组件&Props
- 当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。
- 所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
注意: 组件名称必须以大写字母开头。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
//使用组件
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
State&生命周期
-
state使用不用直接修改使用setState();-
this.state.comment = 'Hello'; -
this.setState({comment: 'Hello'});
-
-
state的更新可能是异步的// Correct this.setState(function(state, props) { return { counter: state.counter + props.increment }; }); -
挂载
componentDidMount() {} -
卸载
componentWillUnmount() {} -
计时器
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
事件处理
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
- 向事件传递参数
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button> this指向绑定constructor(props) { super(props); // 为了在回调中使用 `this`,这个绑定是必不可少的 this.deleteRow = this.deleteRow.bind(this); } <button onClick={() => this.deleteRow()}>Delete Row</button> <button onClick={this.deleteRow}>Delete Row</button> <button onClick={this.deleteRow.bind(this)}>Delete Row</button>- 代码示例
class Toggle extends React.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> ); } } ReactDOM.render( <Toggle />, document.getElementById('root') );
条件渲染
if...else...- 三目运算符
- 与运算符
&& - 阻止渲染,让
render方法直接返回null
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return (
<div className="warning">
Warning!
</div>
);
}
列表&Key
- 使用
map()遍历,内部无需{}
- 列表元素设置key属性,进行唯一标识
- 代码示例
function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <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') );
表单
event.target.value获取值- 代码示例
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> ); } }
状态提升
- 在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
- !!将子组件中的共用状态,提取到最近的共同父组件中。
- !!子组件修改父组件中的状态,调用父组件方法,或node,事件触发
组合VS继承
- 推荐使用组合而非继承来实现组件间的代码重用。
React 哲学
高级指引
标准和指南
-
JSX 支持所有
aria-*HTML 属性 -
使用 React Fragments 来组合各个组件或者短语法
<></>
import React, { Fragment } from 'react';
function ListItem({ item }) {
return (
<Fragment>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
);
}
function ListItem({ item }) {
return (
<> <dt>{item.term}</dt>
<dd>{item.description}</dd>
</> );
}
- 请注意
for在 JSX 中应该被写作htmlFor:
<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>
代码分割
import()- 使用前
import { add } from './math'; console.log(add(16, 26));- 使用后
import("./math").then(math => { console.log(math.add(16, 26)); });React.lazy- 使用前
import OtherComponent from './OtherComponent';- 使用后
const OtherComponent = React.lazy(() => import('./OtherComponent'));React.Suspense- 可以指定加载指示器,以防其组件树中的某些子组件尚未具备渲染条件
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
```
- 异常捕获边界
- 基于路由的代码分割
- 命名导出
Context
- 跳过多层嵌套的传值,Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
- 使用场景:主题
React.createContextconst MyContext = React.createContext(defaultValue);- 创建Context对象,指定默认值
- 只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效
Context.Provider<MyContext.Provider value={/* 某个值 */}>- Provider 接收一个 value 属性,传递给消费组件
Class.contextType- 挂载在 class 上的 contextType 属性会被重赋值为一个React.createContext() 创建的 Context 对象
- 将context挂载在class上
class MyClass extends React.Component { static contextType = MyContext; //挂载到类上 render() { let value = this.context; /* 基于这个值进行渲染工作 */ }
} //另一种挂载方式 MyClass.contextType = MyContext; ```
-
Context.Consumer-
React 组件也可以订阅context的变更,在
函数组件中完成订阅context<ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{ backgroundColor: theme.background }} > Toggle Them </button> )} </ThemeContext.Consumer>
-
-
Context.displayName-
context 对象接受一个名为
displayName的 property,类型为字符串。React DevTools 使用该字符串来确定 context 要显示的内容。const MyContext = React.createContext(/* some value */); MyContext.displayName = 'MyDisplayName'; <MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中 <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
-
-
示例: 修改按钮主题
-
theme-context.jsximport React, { Fragment } from "react"; export const themes = { light: { foreground: "#000000", background: "#E91E63", }, dark: { foreground: "#ffffff", background: "#03A9F4", }, }; export const ThemeContext = React.createContext({ theme: themes.dark, toggleTheme: () => {}, }); ThemeContext.displayName="myContext" -
theme-toggler-button.jsximport React, { Fragment } from "react"; import { ThemeContext } from "./theme-context"; function ThemeTogglerButton() { return ( <ThemeContext.Consumer> {({theme, toggleTheme}) => ( <button onClick={toggleTheme} style={{ backgroundColor: theme.background }} > Toggle Them </button> )} </ThemeContext.Consumer> ); } export default ThemeTogglerButton; -
app.jsximport React,{Fragment,Component} from "react"; import {themes,ThemeContext} from "./theme-context"; import ThemeTogglerButton from "./theme-toggler-button"; class App extends Component{ constructor(props){ super(props); this.toggleTheme=()=>{ this.setState(state=>({ theme:state.theme==themes.dark?themes.light:themes.dark, })); } this.state={ theme:themes.light, toggleTheme:this.toggleTheme, } } render(){ return( <ThemeContext.Provider value={this.state}> <Content></Content> </ThemeContext.Provider> ) } } function Content(){ return ( <Fragment> <ThemeTogglerButton/> </Fragment> ) } export default App;
-
错误边界
-
为了解决部分 UI 的 JavaScript 错误不应该导致整个应用崩溃
-
错误边界是一种 React 组件,这种组件可以捕获并打印发生在其子组件树任何位置的 JavaScript 错误,并且,它会渲染出备用 UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。
-
注意
错误边界无法捕获以下场景中产生的错误:
- 事件处理(了解更多)
- 异步代码(例如
setTimeout或requestAnimationFrame回调函数) - 服务端渲染
- 它自身抛出来的错误(并非它的子组件)
-
示例
import React, { Suspense, lazy } from "react"; class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError:false }; } static getDerivedStateFromError(error){ return {hasError:true} } componentDidCatch(error,errorInfo){ // console.log(error, errorInfo); } render() { if(this.state.hasError){ return <h1>有错误!!!</h1> } return this.props.children; } } export default ErrorBoundary;使用
<ErrorBoundary> <MyWidget /> </ErrorBoundary>
Refs转发
-
Ref 转发是一个可选特性,其允许某些组件接收
ref,并将其向下传递(换句话说,“转发”它)给子组件。 -
示例
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>;
Fragments
- 无需添加额外节点
高阶组件
-
高阶组件是参数为组件,返回值为新组件的函数。
-
示例
// 此函数接收一个组件... function withSubscription(WrappedComponent, selectData) { // ...并返回另一个组件... return class extends React.Component { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ...负责订阅相关的操作... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... 并使用新数据渲染被包装的组件! // 请注意,我们可能还会传递其他属性 return <WrappedComponent data={this.state.data} {...this.props} />; } }; }- 使用
const CommentListWithSubscription = withSubscription( CommentList, (DataSource) => DataSource.getComments() ); const BlogPostWithSubscription = withSubscription( BlogPost, (DataSource, props) => DataSource.getBlogPost(props.id) ); -
包装显示命名方便调试
function withSubscription(WrappedComponent) { class WithSubscription extends React.Component {/* ... */} WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`; return WithSubscription; } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; } -
注意
-
不要在 render 方法中使用 HOC
-
Refs不会被传递
-
务必复制静态方法
- 方法一
function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} // 必须准确知道应该拷贝哪些方法 :( Enhance.staticMethod = WrappedComponent.staticMethod; return Enhance; }- 方法二
import hoistNonReactStatic from 'hoist-non-react-statics'; function enhance(WrappedComponent) { class Enhance extends React.Component {/*...*/} hoistNonReactStatic(Enhance, WrappedComponent); return Enhance; }
-
与第三方库协同
深入JSX
-
用户定义的组件必须以大写字母开头
-
在运行时选择类型
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} />; -
Props的默认值为true<MyTextBox autocomplete /> <MyTextBox autocomplete={true} /> -
属性展开
function App1() { return <Greeting firstName="Ben" lastName="Hector" />; } function App2() { const props = {firstName: 'Ben', lastName: 'Hector'}; return <Greeting {...props} />; } -
渲染
false、true、null、undefined等值,你需要先将它们转换为字符串,否则将会忽略<div> My JavaScript variable is {String(myVariable)}. </div>
性能优化
Portals
-
当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点
ReactDOM.createPortal(child, container) -
使用场景: 一个 portal 的典型用例是当父组件有
overflow: hidden或z-index样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框
Profiler
-
测试组件渲染速度,便于优化
-
用法
render( <App> <Profiler id="Navigation" onRender={callback}> <Navigation {...props} /> </Profiler> <Main {...props} /> </App> ); -
onRender回调
function onRenderCallback( id, // 发生提交的 Profiler 树的 “id” phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一 actualDuration, // 本次更新 committed 花费的渲染时间 baseDuration, // 估计不使用 memoization 的情况下渲染整颗子树需要的时间 startTime, // 本次更新中 React 开始渲染的时间 commitTime, // 本次更新中 React committed 的时间 interactions // 属于本次更新的 interactions 的集合 ) { // 合计或记录渲染时间。。。 }
不使用 ES6
不使用 JSX
协调
Refs & DOM
-
创建Refs
class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } } -
访问Refs
const node = this.myRef.current;
Render Props
-
解决横切关注点
-
render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
-
示例
class Cat extends React.Component { render() { const mouse = this.props.mouse; return ( <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} /> ); } } class Mouse extends React.Component { constructor(props) { super(props); this.handleMouseMove = this.handleMouseMove.bind(this); this.state = { x: 0, y: 0 }; } handleMouseMove(event) { this.setState({ x: event.clientX, y: event.clientY }); } render() { return ( <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}> {/* Instead of providing a static representation of what <Mouse> renders, use the `render` prop to dynamically determine what to render. */} {this.props.render(this.state)} </div> ); } } class MouseTracker extends React.Component { render() { return ( <div> <h1>移动鼠标!</h1> <Mouse render={mouse => ( <Cat mouse={mouse} /> )}/> </div> ); } }
静态类型检查
-
Flow : 是一个针对 JavaScript 代码的静态类型检测器
-
在项目中添加
yarn add --dev flow-bin||npm install --save-dev flow-bin -
配置: 将
flow添加到项目package.json的"scripts"部分{ // ... "scripts": { "flow": "flow", // ... }, // ... } -
运行
yarn run flow init||npm run flow init
-
-
TypeScript
-
需要创建使用 TypeScript 的新项目
npx create-react-app my-app --template typescript
-
添加TypeScript到现有项目中
-
yarn add --dev typescript||npm install --save-dev typescript -
将
tsc添加到package.json中的 “scripts” 部分{ // ... "scripts": { "build": "tsc", // ... }, // ... } -
配置TypeScrpt 编辑器
yarn run tsc --init||npx tsc --init -
目录整理
├── package.json ├── src │ └── index.ts └── tsconfig.json -
其次,我们将通过配置项告诉编译器源码和输出的位置。
{ "compilerOptions": { // ... "rootDir": "src", "outDir": "build" // ... }, } -
运行TypeScript
yarn build||npm run build -
类型定义
yarn add --dev @types/react||npm i --save-dev @types/react -
局部声明 :项目根目录下创建
declarations.d.tsdeclare module 'querystring' { export function stringify(val: object): string export function parse(val: string): object }
-
-
严格模式
使用 PropTypes 类型检查
非受控组件
-
ref 实现
-
默认值:
defaultValue,defaultChecked -
示例
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') );