创建class组件
class App extends Component {
constructor(props) {
super(props);
this.state = { }
}
render() {
return ( );
}
}
export default App;
外部数据props
读this.props
上面的代码写了组件接收props,如果要使用,则可以直接写this.props来读,实际上不在constructor内接收参数props,貌似也不报错。
react官方不建议修改props,它所倡导的思想是此数据应该由它的主人来修改,以避免子组件改动带来的混乱。
props的作用
- 接收外部数据,只能读,写应当由父组件设置写的函数
- 接收外部函数,可以接收父组件修改props的函数
相关钩子componentWillReceiveProps
这个api已经被弃用了,它的新名字是UNSAFE_componentWillReceiveProps
它的参数是
(nextProps)
它的作用是当props修改之前,会执行这个函数,那么新的props会当做它的参数传递进来,如果想要使用旧数据,那么就调用this.props,如果想要使用新数据,那么就调用nextProps。
state内部数据
在class组件中,使用
this.state={
n:xxx,
m:{xx:xxx}
}
来初始化state。
读-this.state
直接使用this.state.xxx.yyy等形式读就可以了,没什么好说的
写-setState
setState(updater[,callback])
第一个参数是更新的内容,第二个参数是一个回调函数,当更新完成后会执行这个回调函数
setState() 会对一个组件的 state 对象安排一次更新。当 state 改变了,该组件就会重新渲染。
setState实际上就是将新的state与旧的进行替换,并且自动帮助我们拷贝没有改变的值(会自动拷贝第一层)
this.state={ //旧sate
n:1,
m:2
}
setState({n:2})//新state
当我使用setState({n:2})的时候,这里的旧state实际上已经被替换成了最新的state,而不是只旧对象的修改。而且react帮助我们将m:2给合并新对象里面了。
不过只能帮我们合并第一层的属性,假设现在我把state里面再加一层复杂类型的值
this.state={
user:{
n:1,
m:2
}
}
//这时候修改n的值
setState({user:{n:this.state.n+1}})
//你会发现m不见了
原因就是react会帮助我们自动复制第一层属性,例如this.state.user,但是不会帮助我们复制this.state.user.m
解决方法:我们使用...运算符自己手工拷贝一份
setState({user:{...this.state.user,n:this.state.n+1}})
这种写法就是先把user内的所有属性都拷贝进来,再把里面的n单独修改覆盖。
调用 setState 其实是异步的,不要指望在调用 setState 之后,this.state 会立即映射为新的值
当调用setState时,如果括号内是对象,假设我多次修改state
add() {
// 注意:这样 *不会* 像预期的那样工作。
this.setState({count: this.state.count + 1});
}
handle() {
// 假设 `this.state.count` 从 0 开始。
this.add();
this.add();
this.add();
// 当 React 重新渲染该组件时,`this.state.count` 会变为 1,而不是你期望的 3。
// 这是因为上面的 `add()` 函数是从 `this.state.count` 中读取数据的,
// 但是 React 不会更新 `this.state.count`,直到该组件被重新渲染。
// 所以最终 `add()` 每次读取 `this.state.count` 的值都是 0,并将它设为 1。
解决方法是在括号内使用函数(state)=>{count:state.count+1}
使用函数每次都会传参,传入最新的state,这样就避免掉始终到同一个this.state里面取一样的值的错误。
给 setState 传递一个对象与传递一个函数的区别是什么?
传递一个函数可以让你在函数内访问到当前的 state 的值。因为 setState 的调用是分批的,所以你可以链式地进行更新,并确保它们是一个建立在另一个之上的,这样才不会发生冲突
shouldComponentUpdate钩子
这个钩子在组件更新前执行,它是用来优化组件渲染的性能的。
如果返回true,就让浏览器更新UI
如果返回false,就让浏览器不更新UI
作用
它主要是用来手动设置是否要进行组件更新,我们可以根据应用场景灵活地设置是否让UI更新,以达到性能的优化
用法示例
class App2 extends React.Component {
constructor(props) {
super(props);
this.state = { count: 1 };
}
// shouldComponentUpdate(nextProps, nextState) {
// if (this.state.count !== nextState.count) {
// return true;
// } else {
// return false;
// }
// }
add = () => {
this.setState((state, props) => {
return { count: state.count + 0 }; //每次点击后加0,所以值是相同的
});
};
render() {
return (
<div>
{console.log("render了一次")}//这里表示render了
<button onClick={this.add}>Count: {this.state.count}</button>
</div>
);
}
}
如果不加shouldComponentUpdate函数的代码,那么上面的代码始终会触发render,因为每次state的地址切换掉了,react认为它已经不再是原来的state了,即使值是一样的。
如果我们加上shouldComponentUpdate函数的代码,那么就不会触发render函数,因为这相当于告诉react,
我这里的值是一样的,你不用再帮我执行render了
注:react会在render后将新的虚拟dom跟旧的相比对,如果发现是一样的,就不会更新UI
React.PureComponent
当然,如果觉得上面的代码很长,可以把React.Component改成React.PureComponent,效果是一样的
PureComponent 会在 render 之前对比新 state 和旧 state 的每一个 key,以及新 props 和旧 props 的每一个 key。 如果所有 key 的值全都一样,就不会 render;如果有任何一个 key 的值不同,就会 render。
生命周期之render
用途:展示视图,创建虚拟dom对象
注意要点
只能有一个根元素。如果实在不行多一个根元素,可以使用<></>或者<React.Fragment></React.Fragment>
<>
<div></div>
<button></button>
</>
使用if-else
render() {
let message;
if (this.state.count === 1) {
message = <div>是1</div>;
} else {
message = <span>是2</span>;
}
return (
<>
{message}
<button></button>
</>
);
}
}
上面的代码太长了,用三元表达式吧
render() {
let message;
return (
<>
{(message = this.state === 1 ? <div>是1</div> : <span>不是1</span>)}
<button>按钮</button>
</>
);
}
使用for
render() {
let arr = [];
for (let i = 0; i < this.state.arr.length; i++) {
//注意要写key
arr.push(<div key={i}>{this.state.arr[i]}</div>);
}
return arr;
}
}
使用for时,需要跟vue一样,添加上key以避免不必要的麻烦
使用map
render() {
return this.state.arr.map((value, index) => {
return <div key={index}>{value}</div>;
});
}
使用map跟上面的for循环返回一个数组效果是一样的
componentDidMount
这个生命周期是用于组件已经挂载到页面上去而执行的特殊函数,官方推荐在这里面执行ajax请求,第一次渲染会执行这个钩子
componentDidMount() 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
componentDidUpdate
第一次渲染不会执行这个钩子,当页面更新后会执行这个钩子,这里也可以用于ajax请求,一般是用来更新数据
在这里面执行setState可能会触发无限循环,因为一直更新了数据,页面无限更新。
shouldComponentUpdate钩子返回false则不会执行此钩子,因为render函数不会被执行,UI也不会被更新。
componentWillUnmount
组件即将被移出页面然后在内存中被销毁时,会执行此生命周期钩子。
它和componentDidMount是有对应关系的
比如说在componentDidMount中设置了timer,那么就应该在componentWillUnmount钩子中清除它,以免造成内存浪费
同理,ajax跟一些监听的取消也在这里面写
分阶段看声明周期钩子
参考文档
zh-hans.reactjs.org/docs/react-…
zh-hans.reactjs.org/docs/state-…