了解jsx
1.jsx只是一种语法糖,编译器会对应编译为React.createElement(component, props, …children),如
<MyButton color="blue" shadowSize={2}>
Click me
</MyButton>
会被编译为
React.createElement(MyButton, {color:"blue", shadowSize:2}, "Click me"}
2.自定义组件必须大写开头,否则会被识别为内置组件,如以下代码会导致编译失败,另外自定义组件必须引入React。
<div></div>
如果自定义组件为小写,或者使用表达式选择组件,则必须先赋值给大写开头的变量,然后才可以在jsx中作为自定义组件使用
3.props使用
可以用{}包裹表达式作为prop,如
<MyComponent foo={1+2+3} />
if/loop等不能直接用于jsx中,可以先计算表达式结果之后在jsx中使用。
<MyTextView autcomplete />
等价于
<MyTextView autocomplete={true}/>
如果已经有现成props,可以使用{…props}作为props传入jsx,如果不希望传入全部props,可以指定部分不传入,比如
const props = {first:"primary", second:"second"};
return <MyButton {first, ...props}/>
4.在jsx表达式中,tag间的内容会赋值给一个特殊的prop: props.children
children可以是普通字符串,也可以是嵌套的组件
React组件也可以返回一组element,如
render() {
return [
<li key="a">A</li>,
<li key="b">B</li>,
]
}
children也可以是{}包裹的表达式
children也可以是function,组件可以在render中调用function生成对应的jsx代码
children为true/false,null,undefined均被忽略,不会渲染
类型检查
针对组件的prop 类型检查,可以给属性propTypes赋值,如
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {name:PropTypes.string}; //name为字符串类型
静态检测
TypeScript作为有类型的js超集,可以在应用构建时发现错误。
1.添加TypeScript
npm install --save-dev typescript
之后就可以使用tsc命令,一般将它添加到package.json的scripts中
{
“scripts”{
“build”:”tsc”,
//...
}
}
2.配置编译器
编译规则定义在tsconfig.json文件中,可以通过如下命令生成
tsc --init
我们主要需要配置其中的rootDir和outDir项,即源代码目录及编译后输出目录。另外outDir一般需要放入.gitignore中。
3.文件后缀名
普通文件后缀名为.ts,带有jsx的文件后缀名为.tsx
4.运行TypeScript
npm run build
5.类型定义
编译器依赖如类型声明来显示错误,对于一个库,类型声明的方式有三种:
- bundled 库中打包了类型声明文件,一般为index.d.ts文件,有些会在package.json中的typings或者types中指定
- DefinitelyTyped 一个远程仓库,托管一些常见库的类型声明,比如React库
npm i --save-dev @types/react
- 如果使用的库没有声明文件,也无法从DefinitelyTyped中获取,则可以自己添加一个声明文件declarations.d.ts,例如
declare module ‘querystring’ {
export function stringify(val: object): string
export function parse(val: string): object
}
refs & dom
refs提供了一种访问在render方法中创建的dom节点或React组件的方式。下面几种场景适合使用refs
- 处理焦点、文本选择、媒体控制
- 触发强制动画
- 集成第三方dom库
1.新建refs
refs一般使用React.createRef()创建,然后在组件的构造方法中赋值给一个property,这样就可以在整个组件中引用到,可以在渲染时用ref参数附加到element上。
class MyComponent extends React.Component{
constructor(props){
super(props)
this.myRef = React.createRef();
}
render() {
return (
<div ref={this.myRef}/>
);
}
}
2.访问refs
在render方法中将ref传给element后,可以使用ref的参数current来引用该节点。
const node = this.myRef.current;
当ref用在HTML element上时,current表示该element,当ref用在普通的class组件上时,current表示该组件的实例。
不可以在函数式组件中使用ref因为函数式组件没有实例。
3.callback refs
除了设置在组件中设置属性ref为React.createRef()的结果外,还可以设置属性ref为一个函数,该函数会传入当前组件实例或者dom。React会在调用componentDidMount前调用该函数。
callback refs可以在组件之间传递,比如parent可以定义一个function作为prop传入child,该function中获取ref保存到自身的变量childNode中,child在render方法中将该function作为组件input的ref参数,这样childNode就引用了child中的input节点。
function CustomTextInput(props) {
return (
<div> <input ref={props.inputRef} /> </div>
);}
class Parent extends React.Component {
render() {
return (
<CustomTextInput inputRef={el => this.inputElement = el} />
);
}}
非受控组件
受控组件中,表单数据由React组件处理,对应的非受控组件中,表单数据由dom处理。
实现一个非受控组件,不需要为每个状态写一个事件回调,可以直接使用ref从dom中取得表单数据。比如
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
默认值
表单元素的value参数会覆盖dom的value,在使用非受控组件时,如果希望表单元素有默认值,但是又不希望后续后续dom value的改变被覆盖。此时可以为表单元素设置参数defaultValue。
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
同样地,checkbox和radio元素支持defaultChecked,select和textarea元素支持defaultValue。
文件输入标记
在HTML中,<input type="file">
可以让用户从设备存储中选择一个或者多个文件上传到服务器中,或通过File API进行操作。
在React中,<input type="file">
始终是一个不受控制的组件,它的值只能由用户设置。
以下代码演示如何创建ref节点以访问提交处理程序中的文件
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input
type="file"
ref={input => {
this.fileInput = input;
}}
/>
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
性能优化
使用生产编译
可以在chrome中安装React Developer Tools for Chrome插件来检测网站是否为生产编译版本。绿色背景图标为生产版本,红色背景图标为开发模式。
以webpack为例描述如何编译生产版本,在生产配置中包含如下插件
// 配置仅用于生产环境,不可以用于开发环境,否则会隐藏有用的警告,同时会使编译更慢
new webpack.DefinePlugin({
'process.env.NODE_ENV':JSON.stringify('production')
}),
new webpack.optimize.uglifyJsPlugin()
更多可以参考官方文档webpack文档
使用Chrome 性能 选项优化组件
在开发模式下,借助浏览器的性能分析工具开发人员可以看到组件是如何挂载,更新,卸载的。
在chrome中操作如下
- 临时停用所有chrome插件,特别是React DevTools,因为这些插件可能影响结果
- 确保应用出于开发模式
- 打开Chrome DevTools 中性能选项卡,点击Record
- 操作希望优化的场景,时间控制在20s内
- 停止录制
- React事件会展示在User Timing标签下
虚拟化长列表
长列表可以采用windowing技术,使用该技术后只需要渲染一定数量的数据,同时可以减少创建dom节点和刷新组件的时间。React VirtualIzed是一种流行的windowing库,它为展示列表,表格等数据提供了一种可复用的组件。开发人员也可以创建自定义的windowing组件。
避免重复渲染
React内部创建并维护当前已渲染ui的实现,它包含了组件中返回的React elements。这种实现方式使得React避免了一些不需要的创建和关联dom节点。有时它被称为虚拟dom。
当一个组件的props或者state改变时,React会将新返回的element和原有的进行比较,不一致时React才会更新dom。
可以通过React DevTools看到虚拟dom的重复渲染
chrome插件
firefox插件
在开发者控制台勾选React选项卡中的"Highlight Updates"选项,操作页面时渲染的组件会显示闪动的边框。针对不需要重复渲染的组件,开发人员应该重写shouldComponentUpdate
方法,该方法会在触发渲染前调用,默认返回true,即React会进行刷新。
shouldComponentUpdate(nextProps, nextFocus) {
return true;
}
如果明确知道该组件不需要刷新,开发人员应该重写该方法并返回false。大多数情况下,开发人员可以继承React.PureComponent。