三大特性 – props 属性
要实现组件的真正可复用,就必须基本定制化(样式+内容+行为),按 javascript 组件设计模式,封装一个函数,通过函数传值,对 dom 元素绑定不同的数据
function renderElement(element, list) {
let html = '';
list.map(text => html += `<li>${text}</li>`);
element.innerHTML = html;
}
let element = document.getElementById('ul'), list = ['苹果', '香蕉', '橙子', '葡萄'];
renderElement(element, list);
// 这里不渲染水果列表,可以通过传值渲染汽车列表
let element = document.getElementById('ul'), list = ['宝马', '奔驰', '红旗', '吉普'];
renderElement(element, list);
// 甚至我还可以不以无序方式渲染,改为有序方式
let element = document.getElementById('ol'), list = ['宝马', '奔驰', '红旗', '吉普'];
renderElement(element, list);
在 react 中,最基本的是使用 props 属性对组件的内容 + 行为进行定制化,从形式上,类似于 HTML 元素的属性 attrbutes 传值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class HeaderComponent extends React.Component {
render() {
return <h1>{this.props.title}</h1> // 通过引入 Properties 属性
}
}
ReactDOM.render(
<HeaderComponent title="i am the header component" />,
document.getElementById('main')
);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
function HeaderComponent(props) {
return <h1>{props.title}</h1>
}
ReactDOM.render(
<HeaderComponent title="i am the header component" />,
document.getElementById('main')
);
</script>
</body>
</html>
与 class 声明类组件不同,傀儡组件函数自带 props 属性,两种类型组件的 props 属性值不可在组件中进行修改,可以说是别人给你的是什么就是什么,你无法改变别人给你的,props 除了接收属性值外,还可以执行接收的方法(当然也需要组件通过行为去执行)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class HeaderComponent extends React.Component {
handle() {
this.props.handle();
}
render() {
return <h1 onClick={handle}>{this.props.title}</h1>
}
}
function handle() {
console.log("i teach you function");
}
ReactDOM.render(
<HeaderComponent title="i am the header component" handle={handle} />,
document.getElementById('main')
);
</script>
</body>
</html>
组件在执行父组件传递过来的方法时,是需要组件自身的某个行为触发了才能执行,并且可以携带一些参数给父组件,这就是父子组件之间的传值方式:
- 父组件传递子组件:单向传递给子组件
- 子组件传递父组件:子组件通过行为引用父组件传递的方法,返回给父组件数据,类似一个回调函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
function HeaderComponent(props) {
const handle = () => props.handle('handle');
return <h1 onClick={handle}>{props.title}</h1>
}
const handle = value => console.log(`i teach you ${value}`);
ReactDOM.render(
<HeaderComponent title="i am the header component" handle={handle} />,
document.getElementById('main')
);
</script>
</body>
</html>
校验是对传入的参数的必要性、有效性的验证,例如上述代码中的 title 标题,判断必选参数是否填写,判断参数类型是否符合要求等等。 校验并不是什么新鲜玩意,这是在对外提供接口、方法、函数等等时,基于不信任原则,必要的自我保护。
同样,在 react 中定义组件的 props 属性时,可以通过 React.PropTypes 提供的验证器来实现对 props 属性进行保护。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prop-types/15.6.0/prop-types.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
function HeaderComponent(props) {
return <h1>{props.title}</h1>
}
HeaderComponent.propTypes = {
title: React.PropTypes.string
}
ReactDOM.render(
<HeaderComponent title={10000} />,
document.getElementById('main')
);
</script>
</body>
</html>
验证方式 | 验证描述 |
---|---|
React.PropTypes.string | 校验当前属性值类型是否为字符类型 |
React.PropTypes.number | 校验当前属性值类型是否为数值类型 |
React.PropTypes.bool | 校验当前属性值类型是否为布尔类型 |
React.PropTypes.array | 校验当前属性值类型是否为数组类型 |
React.PropTypes.object | 校验当前属性值类型是否为对象类型 |
React.PropTypes.func | 校验当前属性值类型是否为函数类型 |
React.PropTypes.symbol | 校验当前属性值类型是否为符号类型 |
React.PropTypes.isRequired | 校验当前属性值是否为必填项``` |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
function HeaderComponent(props) {
return <h1 className={props.fontred}>{props.title}</h1>
}
// 通过组件的 propTypes 设置属性校验(是否存在、类型、非空等)
HeaderComponent.propTypes = {
// props 属性对象、propName 当前属性名、componentName 组件名
title(props, propName, componentName) {
// 判断当前属性是否存在属性对象中,否则抛出错误提示
if(!(propName in props)) throw new Error(`${propName} must be set.`)
// 判断当前属性值类型是否为字符串类型,否则抛出错误提示
if(typeof props[propName] !== 'string') throw new Error(`${propName} must be a string`)
// 判断当前属性值是否非空字符,否则抛出错误提示
if(props[propName].length === 0) throw new Error(`${propName} must not be empty`)
},
}
// 通过组件的 defaultProps 设置默认值,使用组件可传可不传对应的属性值
HeaderComponent.defaultProps = {
fontred: 'fontred'
}
ReactDOM.render(
<HeaderComponent title={10000} />,
document.getElementById('main')
);
</script>
</body>
</html>
组件属性的作用:用于组件之间传值,渲染组件数据,因React单向性可直向向下传递;
组件属性的操作:属性传递,使用 props.属性来获取,推荐使用无状态组件(工厂函数);
组件设置默认值:通过组件的 defaultProps 对象设置组件默认值,设置后可传可不传;
组件属性值校验:通过组件的 propTypes 对象校验组件的类型、非空、正则匹配等;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
function HeaderComponent(props) {
const { name, age, sex } = props;
return <h1>hi, my name is {name}, i'm {age} to year, i'm a happy {sex}</h1>
}
// 多个属性值传递可以通过一个对象传递过去,推荐使用 ES6 的语法
const person = { name: 'Tom', age: 12, sex: 'boy' }
ReactDOM.render(
<HeaderComponent {...person} />,
document.getElementById('main')
);
</script>
</body>
</html>
三大特性 – state 状态
通过 props 属性可以定制组件的内容、样式、行为等。 但是,大家是否注意到,这些都是在创建组件时指定的。那运行时怎么办?答案就是通过 State 状态。
与props 属性类似,State 状态同样可以用来定制组件,但方式和时机是有差异的。 State 状态这个名字体现出是可变的,但有些误导,容易让人认为是“枚举”。实际上 State 状态用来存储组件可能变化的内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class HeaderComponent extends React.Component {
constructor() {
super()
this.state = { who: false } // 1. 初始化状态
}
// 需要通过在调用事件的地方,通过 bind(this) 绑定得到 this 指向组件
handle() {
const who = !this.state.who
this.setState({ who }) // 3. 更新状态
}
// 渲染函数:重写组件继承的方法
render() {
const { who, ste } = this.state // 2. 读取状态
return <h2 onClick={this.handle.bind(this)}>{ this.state.who ? 'Tom': 'Jerry' }</h2>
}
}
ReactDOM.render(
<HeaderComponent />,
document.getElementById('main')
);
</script>
</body>
</html>
在使用 State 状态之前,需要分析出组件存在哪些可变的内容,然后通过 react 提供的 constructor 构造函数内初始化。 初始化后,才能在 render方法中使用 State 状态。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class HeaderComponent extends React.Component {
constructor() {
super()
this.state = { who: false } // 1. 初始化状态
}
// 需要通过在调用事件的地方,通过 bind(this) 绑定得到 this 指向组件
async handle() {
const who = !this.state.who
this.setState({ who }, () => console.log(this.state.who)) // 3. 更新状态
await this.setStateAsync({ who: !this.state.who });
consle.log(this.state.who)
}
// 通过自定义一个 Promise 异步事件触发 State 改变,在由 async-await 达到一个同步操作
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
})
}
// 渲染函数:重写组件继承的方法
render() {
const { who } = this.state // 2. 读取状态
return <h2 onClick={this.handle.bind(this)}>{ who ? 'Tom': 'Jerry' }</h2>
}
}
ReactDOM.render(
<HeaderComponent />,
document.getElementById('main')
);
</script>
</body>
</html>
setState(state, callback) 执行数据后通过一个回调函数返回,是一个异步函数,可以通过自定义一个异步事件触发 State 改变,在由 async-await 达到同步操作
使用 State 状态的方式与使用 props 属性十分相似,都是在 this 下的 state 对象取值。
当某种原因导致组件的 State 状态变化时,需要调用 react 提供的 setState 方法,触发 React 重新渲染组件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class HeaderComponent extends React.Component {
constructor() {
super()
}
// 需要通过在调用事件的地方,通过 bind(this) 绑定得到 this 指向组件
handle() {
console.log(this) // undefined
}
// 如果不绑定 this 的话,方法中的 this 指向的是方法
render() {
return <h2 onClick={this.handle}>this 指向</h2>
}
}
ReactDOM.render(
<HeaderComponent />,
document.getElementById('main')
);
</script>
</body>
</html>
react 的事件在绑定中,需要绑定 this 的指向,如果不绑定 this 为当前组件,事件就没法操作组件类内的属性相关,处理事件绑定 this 有三种方式如下:
1. <h2 onClick={this.handle.bind(this)}>this 指向</h2> 效率低,每次渲染都执行,不推荐
2. <h2 onClick={this.handle}>this 指向</h2> 在构造函数内绑定 this,this.handle = this.handle.bind(this)
3. <h2 onClick={() => this.handle()}>this 指向</h2> 使用箭头函数,不存在 this 指向问题,推荐使用
组件状态的作用:用于处理数据变更后,同步渲染界面更新后的数据
组件状态的操作:1. 初始化状态、2. 获取状态、3. 更新状态 { this.setState() }
组件按状态分类:无状态组件(工厂函数,效率高于ES6类组件)、有状态组件(ES6 类)
react 状态提升
在 react 组件三大特性——属性中,我们了解了父子组件之间的传值关系,我们知道在一个父组件内,是不仅仅只有一个子组件的,在子组件之间如果需要进行值的传递就需要引用 react 的状态提升机制:通过在父组件内共享一份数据,然后通过父子组件之间的传递达到子组件之间的数据传递:子组件 - 父组件 - 子组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
function ListComponent(props) {
return (
<ul>
{ props.list.map((text, index) => <li key={index}>{text}</li>) }
</ul>
)
}
class InputComponent extends React.Component {
constructor() {
super();
this.state = { value: '' };
}
handleSubmit = () => {
this.props.handleSubmit(this.state.value);
this.setState({ value: '' });
}
// 采用受控组件的方式需要手动去匹配 input 的数据改变
handleChange = event => {
let value = event.target.value;
this.setState({ value });
}
render() {
const { value } = this.state;
return (
<div>
<input type="text" value={ this.state.value } onChange={ this.handleChange } />
<button onClick={ this.handleSubmit }>添加</button>
</div>
)
}
}
class App extends React.Component {
constructor() {
super();
this.state = { list: [] };
}
handleSubmit = (value) => {
const { list } = this.state;
list.push(value);
this.setState(list);
}
render() {
const { list } = this.state;
return (
<div>
<InputComponent handleSubmit={this.handleSubmit} />
<ListComponent list={list} />
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('main')
);
</script>
</body>
</html>
属性、状态对比
大部分的组件如果只是做展示作用,props 属性就足够了;但是当组件需要根据用户的操作或者其他内外部的变化反馈出相应的展示效果时,State 状态就是 react 给出的答案。
props 属性用在组件创建的时候,可以通过 JSX(或纯 Javascript)由外部传入,描述的是组件初始化时所需要的信息,而 State状态只能在组件的内部定义,描述的是组件内部影响其内容、样式、行为的动态内容集合,可以理解为私有变量。
比较绕吧,个人理解,这是 react 中一种对组件设计的模式化。这种模式化会在 Redux 的引入时体现的更加明显:
-
属性 props 不需要定义,内置的,状态 state 需在构造函数声明;
-
属性 props 只可读,而状态 state 可读可写;
-
ES6 类组件中使用属性 props 需在构造函数及 super 显式传递;
-
状态 state 只能在 ES6 类组件存在,是私有的数据对象;
-
属性 props 用于上往下级组件传递数据或方法;
-
状态 state 通过 this.setState(state, callback) 修改组件状态;
-
属性 props 用在取得数据去渲染组件;
-
状态 state 处理组件交互逻辑,并通过属性 props 传递数据;
三大特性 – 表单处理
在 react 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state 状态
- 受控组件是表单项输入数据能自动收集成状态
- 非受控组件是需要时才手动读取表单输入框中的数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class FormerComponent extends React.Component {
constructor() {
super();
this.state = { value: '' };
}
handleSubmit = event => {
event.preventDefault(); // 阻止表单默认行为
console.log(this.state.value);
}
// 采用受控组件的方式需要手动去匹配 input 的数据改变
handleChange = event => {
let value = event.target.value;
this.setState({ value });
}
render() {
return (
<div>
<form onSubmit={ this.handleSubmit }>
<input type="text" value={ this.state.value } onChange={ this.handleChange } />
<input type="submit" value="提交" />
</form>
</div>
)
}
}
ReactDOM.render(
<FormerComponent />,
document.getElementById('main')
);
</script>
</body>
</html>
如果当表单数据中存在多个 <input type="text" value={ this.state.value } onChange={ this.handleChange } />
输入源就需要手动配置多个变更事件,不推荐使用这种方式去获取表单数据。
对于繁琐的表单受控组件,推荐使用非受控组件,缩减代码编写,简化编程,提高效率,非受控组件需要借助组件的 Refs 属性,ref 将数组元素信息挂载到组件声明的一个对象上,通过 this.refs.[ref-object] 得到挂载的对象,类似与 HTML DOM,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/babel-standalone/7.0.0-beta.3/babel.min.js"></script>
</head>
<body>
<div id="main"></div>
<script type="text/babel">
class FormerComponent extends React.Component {
constructor() {
super();
this.form = React.createRef();
this.form.current.username = 'wawrh';
}
handleSubmit = event => {
event.preventDefault();
let { username, password, usgender, userlove, elective, messages } = this.form.current;
username = username.value;
password = password.value;
usgender = usgender.value; // 单选框默认选中,使用 defaultChecked 不能使用 checked
// 需要通过过滤的方式去得到复选框的选中值,返回数组对象
userlove = Array.prototype.slice.call(userlove).filter(input => input.checked).map(input => input.value);
elective = elective.value;
messages = messages.value;
console.log(username, password, usgender, userlove, elective, messages);
}
render() {
return (
<div>
<form ref={ this.form } onSubmit={ this.handleSubmit }>
账号:<input type="text" name="username" /><br />
密码:<input type="password" name="password" /><br />
性别:<input type="radio" name="usgender" value="男" defaultChecked /> 男
<input type="radio" name="usgender" value="女" /> 女<br />
爱好:<input type="checkbox" name="userlove" value="语文" /> 语文
<input type="checkbox" name="userlove" value="数学" /> 数学
<input type="checkbox" name="userlove" value="英语" /> 英语<br />
选修:<select name="elective">
<option value="">请选择</option>
<option value="物理">物理</option>
<option value="化学">化学</option>
<option value="生物">生物</option>
<option value="政治">政治</option>
</select><br />
留言:<textarea name="messages" cols="30" rows="10"></textarea>
<input type="submit" value="提交" />
</form>
</div>
)
}
}
ReactDOM.render(
<FormerComponent />,
document.getElementById('main')
);
</script>
</body>
</html>
对于上例设计到表单元素较多时,可以给表单根元素 form 绑定一个 ref={ this.form },并每个表单控件必须存在 name 属性,方便表单对象读取每个表单控件,这里稍微注意的是复选框,需要 Array.prototype.slice.call() 将 NodeList 转化为数组,再进行过滤得到复选框选中的值,另外单选框选中不能使用 checked,而是使用的 defaultChecked 来设置默认选中项目。
refs 除了对 DOM 的取值外,还可以对 DOM 进行其他操作,简单来说,refs 就是用来处理 react 的 DOM 操作,一般是推荐使用受控组件的,非受控组件尽量少用,毕竟 react 框架的主旨是对 DOM 少操作,通过服务器数据去渲染 DOM(SSR 模式)