文章目录
(一)React条件渲染
- 在vue中,通过指令来控制:比如 v-if、v-show
- 在React中,所有的条件判断都和普通的js代码一致
常见的条件渲染的方式有:
方式一:条件判断语句
适合逻辑较多的情况。
方式二:三元运算符
- 适合逻辑比较简单
方式三:与运算符 &&
- 适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不u渲染
方式四:v-show 的效果
- 主要是控制display属性是否为none
(二)React 列表渲染
在真实的开发中,我们会从服务器请求到大量的数据,数据会以列表的形式存储。
在React中并没有像Vue模块语法中的v-for指令,而是需要我们通过js代码的方式组织数据,转成jsx。
- 很多从Vue转型到React的同学非常不习惯,认为Vue的方式更加简洁明了。
- 但是React中的JSX正是因为和js无缝衔接,让它可以更加灵活。
- React 是真正可以提高我们编写代码能力的一种方式。
如何展示列表?
使用数组的map高阶函数。
在展示一个数组中的数据之前,需要先对它进行一些处理。
- 比如过滤掉一些内容:filter函数
- 比如截取数组中的一部分内容:slice函数
对比:
列表渲染(map函数、key)。对应Vue的v-for。
// React
state = {
list: ["list1","list2"]
}
render(){
// 在{函数体}中返回一个li的list
return (
<ul>
{this.state.list.map(item => <li key={item}>{item}</li>)}
</ul>
)
}
// Vue
<ul>
<li v-for="item in list" :key="item">{{item}}</li>
</ul>
可以看出,二者比较相似,只是:
- React 提供了简单的jsx语法,具体实现需要js的能力。内部没有再提供指令,简洁上手。
- Vue提供了很多指令,如v-if、v-for这种。用之前需要先了解指令的相关用法,日常使用会有一定便捷。
(三)事件-event
事件写法:onXxxx
,如onClick。Vue中是@click、v-on:click
- 事件调用方法时的this
- bind(this)
- 箭头函数
class MyComponent extends React.Component{
constructor(props){
super(props)
this.state = {
text: '模版插值'
}
// bind(this)
this.handleClick = this.handleClick.bind(this);
}
handleClick = function(){
this.setState({
text: '修改text'
})
}
// 箭头函数
hanleArrowClick = () => {
this.setState({
text: '修改text from handleStaticClick'
})
}
render(){
return(
<div>
<p>{this.state.text}</p>
<!-- bind(this)也可以在这里写,但是写在constructor性能更佳 -->
<button onClick={this.handleClick}>change</button>
<!-- 这里用的静态方法。静态方法this永远指向该实例 -->
<button onClick={this.handleArrowClick}>change</button>
</div>
)
}
}
分析理解:
- React的事件不是真实绑定在dom上。并且事件调用并不是发生在组件层,而是发生在最外层的事件监听器(事件委托)。
- 而js的this关键字是运行时确定的,在函数被调用时才绑定执行环境(谁调用就指向谁)。所以事件中需要绑定this;或者使用箭头函数写法(定义时就绑定this指向当前组件)。
- event 参数(Vue的event是原生事件)
- event 并不是原生事件,而是React封装的组合事件。
handleClick = function(event){
// react的合成事件
console.log('reactEvent', event);
// 原生事件
console.log('nativeEvent',event.nativeEvent.target);
// 指向 document
console.log('nativeEvent',event.nativeEvent.currentTarget);
}
- 模拟出了DOM的所有能力。如 event.preventDefault()、event.stopPropagation()
- 所有事件都绑定到document上(React17之前)
- 所有事件都绑定到root组件(React17)
- 传递自定义参数
- 正常使用的方法传递即可,注意:event 参数会追加到最后一位。
handleClick = function (param, event){
console.log(param,event);
}
(四)受控组件
- 受控组件简单理解:表单的值,受到state的控制。
-
state驱动试图渲染(可对应高级特性中的非受控组件进行理解)。如表单实现双向绑定。对应Vue的
v-model
。
class MyComponent extends React.Component{
constructor(props){
super(props)
this.state = {
inputVal: 'input'
}
}
render(){
return (
<div>
<p>{this.state.inputVal}</p>
<input type="text" value={this.state.inputVal} onInput={this.handleInput} />
</div>
)
}
// 需要手动实现数据变更(比 v-model 多了一步)
handleInput = e => {
this.setState({
inputVal: e.target.value;
})
}
}
(五)父子组件通信
- 传递数据和接收数据。对应Vue是v-bind和接收props。
- 传递数据:
- 接收数据:
class ChildComponent extends React.Component{
constructor (props){
super(props)
}
render (){
return <div>
{this.props.count}
</div>
}
}
- 传递函数。对应Vue的@xxx=xxx、this.$emit(‘xxx’)
// 父组件
class MyComponent extends React.Component {
constructor (props){
super(props)
this.state = {
count: 99;
}
}
render(){
return (
<div>
<ChildComponent count={this.state.count} changeCount={this.changeCount} />
</div>
)
}
// 接收子组件传递的count来修改自身的count。
changeCount = count => {
this.setState ({
count
})
}
}
// 子组件
class ChildComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return <div>
{this.props.count}
<button onClick={this.addCount}>addCount</button>
</div>
}
addCount = () => {
// 获取父组件传递的props
const {changeCount,count} = this.props
// 调用父组件的changeCount方法
changeCount(count + 1);
}
}
(六)setState
- 不可变值
- class 组件使用setState修改值。
// 修改基础类型数据
this.setState({
// 保证原有值不变,不能直接操作state的值(不能写成this.state.count++)
// this.state.count + 1 是一个新的结果 对
// this.state.count++; ❌
count: this.state.count + 1;
})
// 修改引用类型 - 数组
this.setState({
// 避免使用push、pop 等直接修改原数组的方法
list: [...this.state.list,3], // 新增一个值
list2: this.state.list.slice(1) // 截取
})
// 修改引用类型 - 对象
this.setState({
// 避免直接在原对象上做修改
obj: {...this.state.obj, name: 'shancai'}
})
实际开发中,可能会遇到深层对象的情况需要setState,可以使用immer、immutable这种不可变值的库。
- setState 是同步执行还是异步执行?
- 分两种情况:
- 正常用法是异步执行;
- 在setTimeout和自定义DOM事件的cb中是同步执行。
// 直接用setState是异步的,这里console.log 拿不到修改后的值。
this.setState({
count: this.state.count + 1;
})
// 同步 console 拿不到修改后的值(如果需要拿到修改后的值,可以在setState中添加回调)
console.log(this.state.count)
// 在 setTimeout中用setState是同步的
setTimeout(()=>{
this.setState({
count: this.state.count + 1;
})
// 这里能拿到最新的结果
console.log(this.state.count + 1)
})
// 在自定义的DOM事件用setState也是同步
componentDidMount(){
// 绑定body的点击事件
document.body.addEventListener('click',()=>{
this.setState({
count: this.state.count + 1;
})
// 能拿到最新结果
console.log(this.state.count)
})
}
- setState 是否会被合并执行
-
两种情况:
- 合并执行——传入对象(注意:如果在setTimeout的回调中是同步执行,也就不存在合并执行的说法了)
// 常规用法时,setState 异步执行,结果只加1(被合并执行)
this.setState({
count: this.state.count + 1;
})
this.setState({
count: this.state.count + 1;
})
- 不合并 执行——传入函数
// 结果加2,分别都执行
this.setState((prevState)=>{
return {
count: prevState.count + 1
}
})
this.setState((prevState)=>{
return {
count: prevState.count + 1
}
})
(七)生命周期(对比Vue)
- React
- constructor 类似Vue的init阶段(initLifeCycle、initState…)
- render类似Vue的$mount阶段(执行render -> 得到VNode -> patch到DOM上)
- componentDidMount 等价Vue的mounted
- 在组件更新阶段,render之前还有一个shouldComponentUpdate的阶段(能控制本次组件是否需要render),可以在该阶段做一些性能优化。
(一)非受控组件
- state 不控制视图渲染。(理解:数据不受组件state的控制,直接跟DOM相关)
- 使用场景:必须要操作DOM,setState不能满足。比如input的type=file时的一些交互。
class MyComponent extends React.Component{
constructor (props){
super(props)
this.state = {
inputText: ''
}
// 获取DOM,对应Vue的ref
this.textInput = React.createRef();
}
render(){
return (
<div>
<!-- 对应Vue中的ref,但是Vue传入的是个字符串 -->
<input type="text" ref={this.textInput} defaultValue={this.state.inputText} />
</div>
)
}
handleClick = function (){
// 直接获取DOM的值
console.log(this.textInput.current.value)
}
}
(二)React Portals
- 让组件渲染到父组件以外,对应Vue3的teleport
- 场景:比如日常开发中,父组件设置overflow:hidden的布局。导致组件内的一些弹窗、下拉菜单(绝对定位)等被切割的问题,这时需要逃离父组件的嵌套。
return ReactDOM.createPortal (
<div>
<p>子组件挂载到body层,逃离父组件</p>
</div>
document.body
)
(三)React Context
- 两个核心:1.数据创建:provider; 2.数据消费:consumer
- 具有数据透传的能力,子孙组件能拿到最外层组件的数据。有点类似Vue的v-bind=“$attrs”
// MyContext 文件
import {createContext} from 'react'
export default createContext()
// 外层组件 Provider
import MyContext from './MyContext'
class MyComponent extends React.Component{
constructor(props){
super(props)
this.state = {
val: 'provoiderVal'
}
}
render(){
return (
// 设置 value值
<MyContext.Provider value={this.state.val}>
<div>
<!-- Second 子组件 -->
<Second />
</div>
</MyContext.Provider>
)
}
}
// 子组件Consumer - class Component
import MyContext from './MyContext'
import Third from './Third'
export default class Second extends React.Component{
// 通过静态属性 contextType 接MyContext
static contextType = MyContext
// 通过 this.context 访问
render(){
return (
<div>
{this.context}
<!-- Second组件的子组件 -->
<Third />
</div>
)
}
}
// 孙组件Consumer——function Component
import MyContext from "./MyContext"
import {useContext} from 'react';
export default function Third (){
// 使用useContext获取
const context = useContext(MyContext)
return (
<div>{context}</div>
)
}
(四)异步组件
- 两个核心: lazy + Suspense。跟Vue的异步组件很相似。
// Vue中的异步组件——工厂函数
Vue.component('async-component', function (resolve) {
require(['./async-component'], resolve)
})
// Vue中的异步组件——Promise
Vue.component(
'async-component',
() => import('./async-component')
)
// React的异步组件
// lazy接收一个Promise,并返回一个新的React组件
const Second = React.lazy(() => import('./Second'))
class MyComponent extends React.Component{
constructor(props) {
super(props)
this.state = {
val: 'providerVal'
}
}
render() {
return (
<MyContext.Provider value={this.state.val}>
<div>
<!-- fallback为必传属性(未加载完时显示),接收一个组件 -->
<React.Suspense fallback={<div>loading...</div>}>
<Second />
</React.Suspense>
</div>
</MyContext.Provider>
)
}
}
(五)shouldComponentUpdate
- React 的性能优化的重点。SCU是组件update前的一个生命周期,可控制组件更新
- 默认返回true(组件每次都会执行render)。可通过自定义逻辑(如对比数据变化判断是否需要更新),返回false对组件进行性能优化。
- 基本用法:
class MyComponent extends React.Component{
constructor(props) {
super(props)
}
shouldComponentUpdate (nextProps, nextState, nextContext) {
console.log(nextState.count, this.state.count)
if (nextState.count !== this.state.count) {
return true // 允许渲染,会往下执行render
}
return false // 不渲染,不执行render
}
render() {
return ...
}
}
- 为什么需要这个scu这个生命周期优化
- React的组件更新逻辑:父组件更新,子组件无条件更新。对比Vue,这点有很大区别。
Vue的数据是收集对应组件的渲染Watcher的,也就是行成了一个对应关系。只有当前组件依赖的值发生改变才会出发组件更新。 - 如以下代码,父组件点击按钮会修改count值触发父组件更新。如果不设置SCU,则父组件更新会导致子组件ShowText更新。观察可知,子组件接收的props并未被修改,其实是不需要重新render的。所以,SCU的优化场景就出现了。
// 父组件
class MyComponent extends React.Component{
constructor(props) {
super(props)
this.state = {
count: 0,
text: 'children Component'
}
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleClick}>add count</button>
<!-- 注意子组件接收的text并未发生改变 -->
<ShowCount count={this.state.text} />
</div>
)
}
handleClick = function () {
// 父组件点击修改Count
this.setState({
count: this.state.count + 1
})
}
}
// 子组件
class ShowText extends React.Component{
constructor(props) {
super(props)
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('is update')
}
shouldComponentUpdate (nextProps, nextState, nextContext) {
// 判断props的text是否改变
if (nextProps.text !== this.props.text) {
return true // 允许渲染,会往下执行render
}
return false // 不渲染,不执行render
}
render() {
return (
<div>
<p>{this.props.text}</p>
</div>
);
}
}
- 思考:为什么React不在内部实现SCU,而是提供了个生命周期出来改开发者自行实现?
- 不是所有场景都有必要做性能优化,出现卡顿时再做性能优化是比较合理的开发模式。如果所有情况都直接在SCU做深度比对state,这样本来就会造成性能损耗,往往是不必要的。
- SCU一定要配合不可变值来用。如果内部实现SCU深度比对state再判断是否更新组件,且不遵循不可变值填写可能会造成组件更新失败问题。因为不排除有不遵循不可变值写法的开发者。比如 this.state.list.push(3),再写this.setState({list: this.state.list}) 这种情况。
- PureComponent —— 实现了浅比较的SCU