Appearance
面向组件编程
state 属性
state
是组件对象最重要的属性, 值是对象。组件被称为"状态机", 通过更新组件的 state
来更新对应的页面显示(重新渲染组件)
html
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
this.state = { name: "jack", isMan: true };
this.changeFunc = this.changeFunc.bind(this);
}
render() {
const { name, isMan } = this.state;
return (
<h1 onClick={this.changeFunc}>
我的名字是{name},性别是{isMan ? "男" : "女"}
</h1>
);
}
// 自定义方法
changeFunc() {
const { isMan } = this.state;
this.setState({ isMan: !isMan });
}
}
//渲染虚拟dom到页面
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
- 简化写法:
类中直接写赋值语句,可以给实例对象添加一个属性,和构造器中定义效果一样
html
<script type="text/babel">
class MyComponent extends React.Component {
state = { name: "jack", isMan: true };
render() {
const { name, isMan } = this.state;
return (
<h1 onClick={this.changeFunc}>
我的名字是{name},性别是{isMan ? "男" : "女"}
</h1>
);
}
// 自定义方法
changeFunc = () => {
const { isMan } = this.state;
this.setState({ isMan: !isMan });
};
}
//渲染虚拟dom到页面
ReactDOM.render(<MyComponent />, document.getElementById("test"));
</script>
注意点
- 组件 render 方法中
this
为组件实例对象 state
状态数据不可以直接修改,要通过setState()
- 组件中定义的方法
this
指向问题- 通过箭头函数
- 通过函数对象
bind()
强制绑定this
props 属性
每个组件对象都有props
属性,组件标签的属性都保存在props
中。组件内部不要修改props
数据
基本使用
html
<script type="text/babel">
class Person extends React.Component {
render() {
const { name, age, sex } = this.props;
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
);
}
}
// 类似于标签属性传值
ReactDOM.render(<Person name="Tom" age={19} sex="男" />, document.getElementById("test"));
</script>
批量传递 props
html
<script type="text/babel">
// ...
const obj = { name: "James", age: 21, sex: "女" };
ReactDOM.render(<Person {...obj} />, document.getElementById("test"));
</script>
限制 props
在 React15.5 版本之前, React 自带 PropTypes 属性,可以直接使用:
js
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number,
};
在 React15.5 版本以后, 需要单独引入prop-types
库:
html
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component {
render() {
const { name, age, sex } = this.props;
return (
<div>
姓名:{name}, 性别:{sex}, 年龄:{age}
</div>
);
}
}
// 对标签类型和必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func, // 限制 speak 为函数
};
// 指定默认值
Person.defaultProps = {
sex: "male",
age: 19,
};
ReactDOM.render(
<Person name="Tom" sex="male" age={24} speak={speak} />,
document.getElementById("test")
);
function speak() {
console.log("speaking...");
}
</script>
props 简写
通过static
关键字在类的内部定义限制属性, 定义的属性只能类调用, 实例对象不能调用
html
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func,
};
static defaultProps = {
sex: "male",
age: 19,
};
render() {
// ...
}
}
ReactDOM.render(
<Person name="Tom" sex="male" age={41} speak={speak} />,
document.getElementById("test")
);
function speak() {
console.log("speaking...");
}
</script>
函数式组件使用 props
html
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script src="https://unpkg.com/prop-types@15.8.1/prop-types.js"></script>
<script type="text/babel">
function Person(props) {
const { name, age, sex } = props;
return (
<ul>
<li>姓名:{name}</li>
<li>年龄:{age}</li>
<li>性别:{sex}</li>
</ul>
);
}
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
sex: PropTypes.string,
};
Person.defaultProps = {
sex: "男",
age: 38,
};
const p1 = { age: "23", sex: "男" };
ReactDOM.render(<Person {...p1} />, document.getElementById("test"));
</script>
refs 属性
通过定义ref
可以给标签添加标识, 从而能够获取标签属性和对其进行操作
字符串形式 ref
官方不推荐使用, 未来版本或被移除
html
<script type="text/babel">
class Demo extends React.Component {
showData = () => {
const { input1 } = this.refs;
alert(input1.value);
};
render() {
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点我提示左侧的数据</button>
</div>
);
}
}
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
回调形式 ref
理解 : 给标签节点命名, 并挂载到组件实例自身
html
<script type="text/babel">
class Demo extends React.Component {
// 点击事件
clickTrigger = () => {
alert(this.input1.value);
};
render() {
return (
<div>
{/* el是当前标签, 命名为input1
* 由于是箭头函数, this为 render 中的 this, 即组件实例
* 所以相当于往组件实例上挂载了该标签属性
*/}
<input ref={(el) => (this.input1 = el)} type="text" placeholder="请输入" />
<button onClick={this.clickTrigger}>点击触发</button>
</div>
);
}
}
//渲染虚拟dom到页面
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
createRef 形式 ref
通过调用 React.createRef
返回一个容器, 该容器可以存储被 ref
标识的节点, 该容器只能存储一个节点, 后定义的会覆盖掉之前的
html
<script type="text/babel">
class Demo extends React.Component {
// 创建容器
myRef1 = React.createRef();
clickTrigger = () => {
// current是react定义的固定格式
alert(this.myRef1.current.value);
};
render() {
return (
<div>
<input ref={this.myRef1} type="text" placeholder="请输入" />
<button onClick={this.clickTrigger}>点击触发</button>
</div>
);
}
}
//渲染虚拟dom到页面
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
事件处理
react 中的事件处理
- 通过
onXxx
属性指定事件处理函数- React 使用自定义事件,而非原生 DOM 事件,保证更好兼容性
- React 事件会委托给组件最外层元素, 以保证更高效
- 通过
event.target
得到发生事件的 dom 元素对象, 避免了过渡使用ref
js
class Demo extends React.Component {
showData2 = (event) => {
alert(event.target.value);
};
render() {
return (
<div>
<input onBlur={this.showData2} type="text" placeholder="请输入" />
</div>
);
}
}
受控组件&非受控组件
- 非受控组件: 现用现取, 需要使用时, 直接获取标签属性的值
html
<script type="text/babel">
class Demo extends React.Component {
handleSubmit = (e) => {
e.preventDefault();
const { username, password } = this;
// 需要时获取数据值
alert(`用户名:${username.value},密码:${password.value}`);
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
用户名:
<input type="text" name="username" ref={(c) => (this.username = c)} />
<br />
<br />
密码:
<input type="password" name="password" ref={(c) => (this.password = c)} />
<button>登录</button>
</form>
</div>
);
}
}
//渲染虚拟dom到页面
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
- 受控组件: 类似 vue 的双向绑定, 维护数据在
state
中, 需要时从state
中拿取
html
<script type="text/babel">
class Demo extends React.Component {
state = {
username: "",
password: "",
};
changeName = (e) => {
this.setState({
username: e.target.value,
});
};
changePassword = (e) => {
this.setState({
password: e.target.value,
});
};
handleSubmit = (e) => {
e.preventDefault();
const { username, password } = this.state;
alert(`用户名:${username},密码:${password}`);
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
用户名:
<input type="text" name="username" onChange={this.changeName} />
<br />
密码:
<input type="password" name="password" onChange={this.changePassword} />
<button>登录</button>
</form>
</div>
);
}
}
//渲染虚拟dom到页面
ReactDOM.render(<Demo />, document.getElementById("test"));
</script>
尽量使用受控组件,因为非受控组件会使用大量的 ref
函数柯里化
一个函数接收函数 A 作为参数,运行后能够返回一个新的函数。并且这个新的函数能够处理函数 A 的剩余参数
js
//普通函数
function add(a, b, c) {
return a + b + c;
}
add(1, 2, 3); //6
//手动柯里化后的函数,其参数可以逐步单个传入,得到相同结果。
function _add(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
_add(1)(2)(3); //6
通过函数柯里化可以优化表单代码, 将saveUsername
和 savePassword
合并为一个函数
jsx
class Login extends React.Component {
state = {
username: "",
password: "",
};
saveFormData = (dataType) => {
// 作为回调给 onChange 调用, 所以此处能拿到 onchange 的 event 事件
return (event) => {
this.setState({ [dataType]: event.target.value });
};
};
handleSubmit = (event) => {
event.preventDefault();
const { username, password } = this.state;
alert(`用户名是:${username}, 密码是:${password}`);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:
{/* 将函数的返回值交给 onChange */}
<input onChange={this.saveFormData("username")} type="text" name="username" />
密码:
<input onChange={this.saveFormData("password")} type="password" name="password" />
<button>登录</button>
</form>
);
}
}
生命周期
旧版生命周期
三个阶段
- 初始化阶段: 由
ReactDOM.render()
触发---初次渲染 - 更新阶段: 由组件内部
this.setSate()
或父组件重新render
触发 - 卸载组件: 由
ReactDOM.unmountComponentAtNode()
触发
新版生命周期
更新内容
- 即将废弃
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
- 新增(很少使用)
getDerivedStateFromProps
、getSnapshotBeforeUpdate
常用三个钩子
render
: 初始化渲染和更新渲染componentDidMount
: 进行初始化,如开启定时器、发送网络请求、订阅消息componentWillUnmount
: 进行收尾,如关闭定时器、取消订阅消息
虚拟 DOM 和 Diff 算法
diff 预设限制
- 只对同级元素进行 diff
- 不同元素标签产生不同的树, 即如果元素由 div 变成 p,那么 React 会删除 div 及其子孙节点,新建 p 及其子孙节点
- 可以使用
key
标识虚拟 DOM
使用key
标识进行比较的规则
- 旧虚拟 DOM 找到和新虚拟 DOM 相同的 key
- 如果内容没有改变, 直接复用
- 如果内容改变,则生成新的真实 DOM, 替换掉之前的真实 DOM
- 旧虚拟 DOM 未找到和新虚拟 DOM 相同的 key:根据数据创建新的真实 DOM
js
class Person extends React.Component {
state = {
persons: [
{ id: 1, name: "小张", age: 18 },
{ id: 2, name: "小李", age: 19 },
],
};
add = () => {
const { persons } = this.state;
const p = { id: persons.length + 1, name: "小王", age: 20 };
this.setState({ persons: [p, ...persons] });
};
render() {
return (
<div>
<button onClick={this.add}>添加</button>
<ul>
{this.state.persons.map((item, index) => {
return (
<li key={index}>
{item.name}---{item.age}
<input type="text" />
</li>
);
})}
</ul>
</div>
);
}
}
使用index
作为key
可能出现的问题
- 若对数据进行逆序添加、逆序删除等破坏顺序的操作,
index
值会重新顺序生成。
当根据key
值再比对时, 由于内容不同导致真实DOM
重新更新无法复用。 页面虽显示正常, 但会影响更新效率
html
<!-- 初始虚拟DOM -->
<li key="0">小张---18</li>
<li key="1">小李---28</li>
<!-- 往顶部添加一条数据后的虚拟DOM -->
<li key="0">小王---14</li>
<li key="1">小张---18</li>
<li key="2">小李---28</li>
- 如果结构中包含输入类的 DOM(如 input 输入框), 由于
input
标签结构没变会按照index
继续复用, 但其value
值可能有着更新前的输入, 最终会导致错误的 DOM 更新
html
<!-- 初始虚拟DOM -->
<li key="0">小张---18<input type="text" /></li>
<li key="1">小李---28<input type="text" /></li>
<!-- 更新数据后的虚拟DOM -->
<li key="0">小王---14<input type="text" /></li>
<li key="1">小张---18<input type="text" /></li>
<li key="2">小李---28<input type="text" /></li>