01.组件
1 什么是react
?
react
是一个UI
库,能把数据绑定到页面上并实时渲染出来-这就是react
的核心。
相对于通过原生的DOM
对象的来修改数据和事件的绑定,往往很繁琐且事件失效重新绑定的问题,为了解决这2个问题,react
在内存生成虚拟DOM
,然后事件的绑定和数据的渲染都直接反映到虚拟DOM
上,而虚拟DOM
的修改再反映到真实的浏览器DOM
对象上上。这样带来的好处就是,开发者需要的事件在DOM
显式绑定,需要的更改在DOM
显式绑定且不用去关心原生带的绑定繁琐和事件失效的问题。
从MVC
软件模式的角度来看,视图层为数据的渲染和事件的触发,控制层则对事件触发的处理,模型层对数据的CURD
处理。react
的虚拟DOM
使用V
层成立,按MVC
模式也就能开发更复杂和更大型的应用。
2 什么是组件?
一个复杂的页面可以分割成若干个简单的部分。这些部分有自己的样式、数据和处理逻辑,这些称为组件。再把这些组件合并起来就是原来的复杂页面了。
2 脚手架初始化
2.1 新建空项目(typescript
)
$ npx create-react-app my-app --typescript
2.2 配置样式预处理器less
(可选)
2.2.1 安装less
的webpack
转译器和less
$ yarn add less less-loader -D
2.2.2 弹出webpack
的配置并修改打包配置使less
生效
$ yarn run reject
目录如下:
.
├── README.md
├── config # <--- 弹出的webpack配置目录
├── node_modules
├── package.json
├── public
├── scripts
├── src
├── tsconfig.json
└── yarn.lock
要使用less
生效需要在config/webpack.config.js
配置3个地方
2.2.2.1 配置style files regexes
...
// style files regexes
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
// 添加less配置
const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/
...
2.2.2.2 修改getStyleLoaders
函数
...
const getStyleLoaders = (cssOptions, lessOptions, preProcessor) => {
...
{
loader: require.resolve('css-loader'),
options: cssOptions,
},
// <!--config start-->
{
loader: require.resolve('less-loader'),
options: lessOptions,
},
// <!--config end-->
...
2.2.2.3 模仿sass
配置加入
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
{
test: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'sass-loader'
),
},
// <!--- less config start -->
{
test: lessRegex,
exclude: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'less-loader'
),
sideEffects: true,
},
{
test: lessModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction && shouldUseSourceMap,
modules: {
getLocalIdent: getCSSModuleLocalIdent,
},
},
'less-loader'
),
},
...
// <!-- less config end -->
2.2.3 加入tslint
支持
$ yarn add --dev typescript tslint tslint-config-prettier tslint-plugin-prettier tslint-react prettier
$ yarn add @types/node @types/react @types/react-dom @types/jest
项目根目录添加tslint.json
{
"extends": [
"tslint:recommended",
"tslint-react",
"tslint-config-prettier"
],
"rulesDirectory": [
"tslint-plugin-prettier"
],
"rules": {
"prettier": true,
"interface-name": false
}
}
根目录添加.prettierrc
{
"trailingComma": "es5",
"printWidth": 100,
"semi": false,
"singleQuote": true
}
根目录package.json
添加
"scripts": {
...
"tslint-check": "tslint-config-prettier-check ./tslint.json",
"lint": "tslint -c tslint.json src/**/*.{ts,tsx} --fix --format verbose"
},
注: tslin
参考自github gist 的配置 ,至于行不行,还没验证,如果没有问题的话,就加进试试
3 初始化组件
$ npx generate-react-cli component Test
? Does this project use TypeScript? Yes
? Does this project use CSS modules? No
? Does this project use a CSS Preprocessor? less
? What testing library does your project use? Testing Library
? Set the default path directory to where your components will be generated in? src/pages
? Would you like to create a corresponding stylesheet file with each component you generate? Yes
? Would you like to create a corresponding test file with each component you generate? No
? Would you like to create a corresponding story with each component you generate? No
? Would you like to create a corresponding lazy file (a file that lazy-loads your component out of the box and ena
bles code splitting: https://reactjs.org/docs/code-splitting.html#code-splitting) with each component you generate
? No
The "generate-react-cli.json" config file has been successfully created on the root level of your project.
You can always go back and update it as needed.
Happy Hacking!
Stylesheet "Test.less" was created successfully at src/pages/Test/Test.less
Component "Test.tsx" was created successfully at src/pages/Test/Test.tsx
注: 函数式组件和类组件的定义和说明请参考《官方文档-component and props》
3.1 组件的props
tip
注: 组件的props
官方文档《官方文档-component and props》有说明,这里进行下类型的补充
import React from 'react'
interface testProps {
timeString: string // <--- 指定组件参数名,类型为string
timeStampNumber2: number // <--- 指定组件参数名可以有,类型为number
}
export default class Test2 extends React.Component<testProps, any> { // <--- 指定继承类型
public constructor(props: testProps) { // <--- 指定传进来的组件参数类型约束
super(props)
}
public render(): React.ReactNode {
return (<React.Fragment> {this.props.timeString} </React.Fragment>) // 以空标签的形式输出传入的参数
}
}
4 组件的state
tip
注: 关于组件的state
请参考《组件的state》
以类组件来说明,state
可以看作是类下的公开属性,而如果直接修改这个属性也是可以直接反馈到模板上的。而其它的公开属性(不一定要state)也是可以绑定到模板上的。一样的。不一定非要通过setState()
方法,但还是建议通过setState()
来写,官方嘛。
5 组件的事件
官方文档这个事件的绑定还是很奇怪的-写法很奇怪。奇怪在于绑定,要做到3步: 1写个事件处理方法(正常操作),2Dom
显示绑定(正常),3,还要在初始化的地方再进行绑定一次(很怪)。虽然实验性写法给出了不用第3步的写法,但也是有怪怪的。
传个参数也是怪怪的,相对于angular
和vue
还是很怪。
tip
6 条件渲染和列表
条件渲染官方文档能给出的最多是三元的方式,而条件判断也有更多else if
要判断,只能在组件内部进行再定义个组件(方法或函数),而else if
这种DOM
无法写的逻辑则在内部进行写好后再进行返回;
6.1 条件渲染
...
public renderCondition(): any
{
if (this.state.coNumber === 1) {
return (<p>11</p>);
} else if (this.state.coNumber === 2) {
return (<p>22</p>);
} else {
return (<p>33</p>);
}
}
public render(): React.ReactNode
{
return (<React.Fragment>
{this.renderCondition()}
</React.Fragment>)
}
6.2 列表渲染
...
public renderCondition(): any
{
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number, index) =>
<li key={number + index}>
{number}
</li>
);
return listItems;
}
public render(): React.ReactNode {
return (<React.Fragment>
{this.renderCondition()}
</React.Fragment>)
}
组件的渲染很纯粹-就展示数据参与逻辑处理很少。
7 表单
表单官的用法官方文档写的很明白,这里补充下typescript
的类型示例a
import React from 'react'
interface testProps {
timeString?: string,
test? : number
}
interface testState {
text: string,
inputValue: string
}
export default class Test2 extends React.Component<testProps, testState> {
public state: testState;
public constructor(props: testProps)
{
super(props);
this.state = {
text: '',
inputValue: ''
};
this.handleEdit = this.handleEdit.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
}
public handleEdit(event: React.ChangeEvent<HTMLTextAreaElement>): void
{
this.setState({
text: event.target.value
})
}
public handleInputChange(event: React.ChangeEvent<HTMLInputElement>): void
{
this.setState({
inputValue: event.target.value
})
}
public render(): React.ReactNode {
return (<React.Fragment>
<textarea value={this.state.text} onChange={this.handleEdit}/>
<input value={this.state.inputValue} onChange={this.handleInputChange}/>
</React.Fragment>)
}
}
8 组件的状态提升
状态提升解决了一个问题-子组件向父组件提供数据,其实就子组件有权去触发父组件的方法来实现通讯。其实现方式可以看作一个闭包,在组父组声明子组件回调。如果了解过view
和angular
就会发现实现的方式是一样,叫法不一样,一个是组件通讯一个是输出, 相比下react
的事件传下去后并直接调用的写法更好理解。