Play With React
本文内容根据官方文档整理
- 初始化项目
- 查看本机npm版本
- create-react-app脚手架初始化代码库
- 执行,构建与测试
- React基础知识
- jsx语法
- jsx中的元素
- 组件
- 组件state与LifeCycle
- 单向数据流:组件的状态可以作为props传递给子组件(React实现组件化的基础,同时带来了组件间通信的问题)
- React事件处理
- 组件的条件渲染
- 列表的Key值
- 表单的使用——受控组件
初始化项目
查看本机npm版本
npm -v
6.4.1
create-react-app脚手架初始化代码库
- npm版本如果高于
^5.2
,需要使用npx命令-
npx create-react-app play-with-react
cd play-with-react
-
-
npm < 5.2
,直接使用-
create-react-app play-with-react
cd play-with-react
-
执行,构建与测试
执行
yarn start
构建
yarn build
create react app
默认的webpack配置会将src中的代码打包到项目根目录的build目录下,供部署使用。
测试
yarn test
create react app
已经为我们内置了Facebookjs单元测试框架Jest
-
测试文件名约束
- 测试文件必须放在src目录下级目录中
- 测试文件可以是tests文件夹下的*.js文件
- 测试文件可以是任何目录下的.test.js文件或者.spec.js文件
-
聚焦与排除
- 约定每个测试用例写在it()
- 使用fit()仅执行该测试用例
- 使用xit()排除该测试用例
-
测试覆盖率
npm test -- --coverage
- 可在package.json中进行配置覆盖率限制
{ "jest": { "collectCoverageFrom": [ "src/**/*.{js,jsx}", "!<rootDir>/node_modules/" ], "coverageThreshold": { "global": { "branches": 90, "functions": 90, "lines": 90, "statements": 90 } }, "coverageReporters": ["text"] } }
Reactj基础知识
jsx语法
jsx是js语言的扩展,使用它我们可以像下面这样在js代码中描述UI。
const element = <h1>Hello World!</h1>
jsx中可以嵌入js表达式
const name = 'Liang Lingrui';
const element = <h1>Hello, {name}</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
jsx的大括号中可以包含任意合法的javascript表达式,包括函数执行表达式
const formatName = (user) => (user.firstName + ' ' + user.lastName)
const user = {
firstName: 'Liang',
lastName: 'Lingrui'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
jsx编译之后本身也是表js达式
const getGreeting = (user) => {
const defaultElem = <h1>Hello, Stranger.</h1>
const userElem = <h1>Hello, {formatName(user)}!</h1>
if(user){
return userElem
}else{
return defaultElem
}
}
jsx中的元素
元素的主要特点
- React应用中的最小组成单元
- 元素不是组件,是组件的组成部分
- 可以将组件定义为元素
- 元素描述了我们在屏幕上会看到什么,就是html标签
- 元素一旦创建,就不可变
- 只有创建一个新的element替换才可以改变UI
- React的diff机制会把新的element跟之前的element比较,仅替换所需的部分
jsx中的element可以像html中一样使用属性,但是其属性名都采用驼峰命名规则
const element = <div className="box"></div>;
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
jsx中的element可以有子元素
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
jsx本身具备防注入攻击的特性,所有的用户输入默认在render之前都会被转为字符串,所以需要使用跨域功能时需要手动关闭这一特性
jsx元素的本质
const element = <h1 className="greeting">Hello, world!</h1>
- 方便我们使用js语言表现UI的语法糖,实际调用了一个工厂方法去实例化一个js对象
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
- 构造出来的对象数据结构:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
组件
组件的价值在于构造出独立可复用的UI单元,其具有自己的功能,同时维护自己的状态数据。
组件的本质
- 在React中,组件就是函数,返回值为jsx中的emement
- 所有的组件必须是纯函数
定义组件的两种方式
- 功能(函数式)组件:一个接受props作为参数的函数,返回值是jsx中的element
const Welcome = (props) => (<h1>Hello, {props.name}</h1>)
-
class式组件:继承于React中的Component类,render方法中返回element,ES6中的js语法糖,class的本质还是
function
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
组件可以定义为元素
实际上是函数的调用,标签中的属性值都是props对象下的属性,都可以通过props访问到
const element = <Welcome name="LLR" />;
---------------------------------------
let props = {
name : "LLR"
}
const element = Welcome(props)
组件的聚合
const Welcome = (props) => (<h1>Hello, {props.name}</h1>)
const App = (props) => (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
)
props是不可修改,Read-Only
因此,所有的函数式React组件都是纯函数。但是UI是交互式的、多变的,所以React引入了state来描述组件的表现状态。
组件state与LifeCycle
在class式的组件中才能使用state
- 在class式的组件构造函数初始化state值
class Clock extends React.Component{
constructor(props) {
super(props);
this.state = {date: new Date()};
}
...
更新组件状态值可以自动触发生命周期函数
Clock组件自己维护自己的状态信息
class Clock extends React.Component{
constructor(props) {
super(props);
this.state = {date: new Date()};
}
tick() {
this.setState({
date: new Date()
});
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
render(){
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
)
}
}
不要直接修改state的值
- 直接修改state的值可能不会触发组件的生命周期函数
// Wrong
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
React会将多个setState合并处理,props跟state可以异步更新,所以不要在setState的时候使用state/props来计算新的值
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
setState方法是patch式的更新,会替换参数中的部分,state中原有的不会改变
下面的代码在constructor中初始化了state对象包含posts跟comments两个属性,但是生命周期函数componentDidMount在调用的时候可以只更新posts属性。
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
}
单向数据流:组件的状态可以作为props传递给子组件(React实现组件化的基础,同时带来了组件间通信的问题)
如Clock案例中:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
也适用于自定义组件的属性值传递,FormattedDate组件并不知道date属性的值是来时用户输入还是父组件状态值
const FormattedDate = (props) => (
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
)
<FormattedDate date={this.state.date} />
React事件处理
onClick属性方法可以handel元素/组件的点击事件,需要特别注意:将事件处理方法传递到子组件时要手动绑定this上下文,否则调用对象为undefined,这是js语法导致的问题,跟React无关
class Switch extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
}
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}
render() {
return (
// 调用bind方法绑定时间到this指向的对象
<button onClick={this.handleClick.bind(this)}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
使用ES6箭头函数可以避免这种绑定this的写法
class Switch extends React.Component {
handleClick() {
console.log('haha')
}
render() {
return (
// 使用箭头函数做事件处理
<button onClick={(e) => this.handleClick(e)}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
组件的条件渲染
js尽量考虑函数式编程
- 用 && 号实现 if 的效果
- 见代码中mailBox组件
- 使用三目运算符,来实现行内的 if else 效果
- 见代码中LoginControl组件
列表的Key值
React的list中每一个列表的条目都应该有一个Key来做唯一表示,通常是不可变的id属性。如果缺少Key浏览器会报错。引入Key的原理请查看深入理解React相关描述
表单的使用——受控组件
- 为表单空间初始化state值,比如username
- 定义 handleChange 处理函数,里面修改 username 值
- render中,添加一个input使value值等于this.state.username
- 这个input就是一个受控组件,监听onChange事件更新用户输入保证输入数据的实时更新
class Form extends React {
state = { username: '', email: '' }
handleChange = event => {
const { value, name } = event.target
this.setState({
[name]: value
})
}
handleSubmit = e => {
console.log(`${this.state.username} ${this.state.email}`)
e.preventDefault()
}
render() {
return (
<div>
Username:
<input
name="username"
type="text"
value={this.state.username}
onChange={this.handleChange}
/>
<br />
Email:
<input
name="email"
type="text"
value={this.state.email}
onChange={this.handleChange}
/>
<br />
<button onClick={this.handleSubmit}>提交</button>
</div>
)
}
}