关于Vue和React的一些对比

Vue和React都是目前最流行、生态最好的前端框架之一。框架本身没有优劣之分,只有适用之别,选择符合自身业务场景、团队基础的技术才是我们最主要的目的。

博主1年前用的是Vue框架,近半年转技术栈到React框架,对于Vue和React都有一些基本的了解。接下来博主将与大家通过上、中、下三部一起走近Vue和React,共同探讨它们的差异。(比如你正在用vue,对react感兴趣也可以看下,反之亦然)

主要从条件渲染是否显示列表渲染计算属性侦听器ref表单插槽八个方面对比vue和react,欢迎多多交流,共同探讨,感谢支持。

1.条件渲染(v-if vs &&)

条件渲染用于根据条件判断是否渲染一块内容。

vue

vue中用v-if指令条件性地渲染一块内容。只有当表达式返回真值时才被渲染,v-if还可以和v-else-ifv-else配合使用,用于不同条件下的渲染,类似js里的if else语法。

(1)基本用法

 <div v-if="showContent">This is content.</div>

data

  data() {
    return {
      showContent: false
    }
  }

showContentfalse时,不会渲染DOM节点,会留下一个注释标志。

![v-if为false](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="306" height="70"></svg>)

showContenttrue时,才会渲染DOM节点。

![v-if为true](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="296" height="66"></svg>)

(2)v-else 二重判断

v-ifv-else配合使用时,v-else必须要和v-if相邻,否则会报错。

  <div>
    <div v-if="showContent">This is content.</div>
    <div v-else>Hide content.</div>
  </div>

(3)v-else-if 多重判断

当有多重判断条件时,可以使用v-else-if,类似v-ifelse-if块v-else 元素必须紧跟在带v-if或者v-else-if的元素的后面,否则它将不会被识别。

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

(4)template 使用 v-if

另外,当想切换多个元素时,在<template>上使用v-if可以针对元素进行分组。

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

react

react使用与运算符&&、三目运算符(?:)、if else来实现条件渲染的效果。

(1)与运算符

与运算符&&,实现效果类似v-if,左边变量为真值时,渲染右边的元素。

 return (
      <div>
        {showItem && <div>This is content.</div>}
      </div>
    );

(2)三目运算符(?:

使用三目运算符(?:),实现类似v-if v-else效果,showItem变量为true时显示第一个元素,变量为false时显示第二个元素。

 return (
      <div>
        {
          showItem ?
            (<div>This is true content.</div>) : (<div>This is false content.</div>)
        }
      </div>
    );

(3)多重判断

当处理多重判断时,可以使用函数加if else多重判断或者switch case的形式来实现,实现类似v-if v-else-if v-else的效果。

Ⅰ.if-else多重判断

render() {
    const { type } = this.state;
    const toggeleShow = (type) => {
      if (type === 'A') {
        return <div>A</div>;
      } else if (type === 'B') {
        return <div>B</div>;
      } else if (type === 'C') {
        return <div>C</div>;
      } else {
        return null;
      }
    };

    return (
      <div>
        {toggeleShow(type)}
      </div>
    );
  }

Ⅱ.switch case多重判断

render () {
    const { type } = this.state;
    const toggeleShow = (type) => {
      switch (type) {
        case 'A':
          return <div>A</div>;
        case 'B':
          return <div>B</div>;
        case 'C':
          return <div>C</div>;
        default:
          return null;
      }
    };

    return (
      <div>
        {toggeleShow(type)}
      </div>
    );
  }

2.是否显示(v-show vs style+class)

另一个用于展示条件的元素的选项是v-show,react中可以通过style或者切换class的形式实现是否显示。

vue

v-show渲染的元素会被渲染并保留在DOM中。v-show只是简单地切换元素的css属性display

 <div v-show="showContent">This is content.</div>

showContentfalse时,styledisplay属性值为none

![v-show为false](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="430" height="29"></svg>)

showContenttrue时,styledisplay属性值为block(元素默认display属性值)。

![v-show为true](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="735" height="171"></svg>)

注意,v-show 不支持 <template> 元素,也不支持 v-else。

v-ifv-show对比总结:

  • 1)v-if是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
  • 2)v-if也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
  • 3)相比之下,v-show就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
  • 4)一般来说,v-if有更高的切换开销,而v-show有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件很少改变,则使用v-if较好。

react

react中通过修改style或者class来实现v-show类似效果。

通过修改style属性的display属性来实现切换是否显示。

<div style={{ display: showItem ? 'block' : 'none' }}>
    This is content.
</div>

通过变量判断修改class来实现切换效果,本质上也是切换元素的display属性来实现切换效果。

在react中动态修改元素样式时(比如切换tab、按钮选中状态),适用于使用class来实现。

 const itemClass = showItem ? 'show-item-class' : 'hide-item-class';
    return (
      <div className={itemClass}>
        This is content.
      </div >
    );

class样式:

.show-item-class {
  display: block;
}
.hide-item-class {
  display: none;
}

3.列表渲染(v-for vs map)

vue中使用v-for来渲染列表,react中使用map来渲染列表。不管是v-for还是map来渲染列表都需要添加key值(key在兄弟节点之间必须唯一),方便快速比较出新旧虚拟DOM树间的差异。

vue

vue中可以使用v-for来渲染数组、对象、<template>、组件。

(1)渲染数组

渲染数组时,使用(item, index) in items形式的特殊语法,其中items是源数据数组,item则是被迭代的数组元素的别名,index表示当前元素的索引。

  <div>
    <div v-for="(item, index) in items" :key="item.message + index">
      {{item.message}}
    </div>
  </div>

data

 data() {
    return {
      items: [
        {
          message: 'Hello'
        },
        {
          message: 'Welcome'
        }
      ]
    }
  }

(2)渲染对象

v-for也可以用来遍历一个对象的属性,使用(value, key, index) in obj的形式,其中key表示对象的key值,value表示对象的value值,index表示当前索引。

在遍历对象时,采用Object.keys()的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。

<div v-for="(value, key, index) in obj" :key="key + index">
  {{index}}.{{key}}: {{value}}
</div>

data

  data() {
    return {
      obj: {
        name: 'xiaoming',
        age: 18,
        sex: 'male',
        height: 175
      }
    }
  }

(3)渲染多个元素

<template>上使用v-for来渲染一段包含多个元素的内容。

<template>上使用v-for来渲染元素段时,不允许绑定key值。因为template上并不会生成实际Dom节点。可以给底下的子元素绑定key值。

<div>
    <template v-for="(item, index) in items">
      <div :key="item.message">{{ item.message }}</div>
      <div :key="item.message + index" class="divider"></div>
    </template>
  </div>

data

 data() {
    return {
     items: [
       {
         message: 'hello'
       },
       {
         message: 'world'
       },
       {
         message: 'welcome'
       }
     ]
    }
  },

生成DOM时,并不会生成实际DOM节点。

![template使用v-for](data:image/svg+xml;utf8,<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="264" height="137"></svg>)

(4)渲染自定义组件列表

在自定义组件上,可以使用v-for渲染自定义组件列表,通过props将数据传递给组件。

在组件上使用v-for时,key是必须的。

v-for渲染自定义组件列表,将item通过props传递给组件。

 <my-component
    v-for="(item, index) in items"
    :key="item.message + index"
    :item="item">
  </my-component>

my-component组件使用props接收父组件传来的数据。

<template>
  <div>{{item.message}}</div>
</template>

<script>
export default {
  props: ['item'],
  data() {
    return { }
  }
}
</script>

react

react中使用map()方法来实现列表渲染。

(1)渲染数组

遍历数组中的每个元素,得到一组jsx元素列表。数组中的每一个元素需要添加唯一的key值。

  render() {
    const items = [
      {
        message: 'hello'
      },
      {
        message: 'world'
      },
      {
        message: 'welcome'
      }
    ];
    const listItems = items.map((item, index) => <div key={item.message + index}>{item.message}</div>);
    return (
      <div>
        {listItems}
      </div>
    );
  }

(2)渲染对象

对于对象,可以采用方法通过Object.keys()或者Object.entries()来遍历对象。

  render() {
    const obj = {
      name: 'xiaoming',
      age: 18,
      sex: 'male',
      height: 175
    };
    const renderObj = (obj) => {
      const keys = Object.keys(obj);
      return keys.map((item, index) => <div key={index}>{obj[item]}</div>);
    };
    return (
      <div>
        {renderObj(obj)}
      </div>
    );
  }

(3)渲染自定义组件列表

渲染自定义组件列表与vue中类似,需要给组件添加key值标识。

  render() {
    const items = [
      {
        message: 'hello'
      },
      {
        message: 'world'
      },
      {
        message: 'welcome'
      }
    ];
    const listItems = items.map((item, index) =>
          <ListItem message={item.message} key={item.message + index} />);
    return (
      <div>
        {listItems}
      </div>
    );
  }

4.计算属性(computed vs useMemo+useCallback)

计算属性表示根据组件的数据(包含组件自身的数据和接收父组件的props)需要二次计算并“保存”的数据,使用计算属性的好处是避免每次重复计算的开销(比如遍历一个巨大的数组并做大量的计算)。

vue

vue中用computed来表示计算属性,可以定义多个计算属性,计算属性可以互相调用。计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。vue中可以直接使用this.xxx直接获取到计算属性。

(1)基本用法

下面声明计算属性reversedMessage依赖于message,这就意味着只要 message还没有发生改变,多次访问reversedMessage计算属性会立即返回之前的计算结果。

<div>
    message: <input type="text" v-model="message" />
    <div>{{reversedMessage}}</div>
</div>

script

  data() {
    return {
      message:''
    }
  },
  computed: {
    reversedMessage() {
      return this.message.split('').reverse().join('')
    }
  }

(2)计算属性的setter

技术属性默认只有getter,也可以使用setter,当修改计算属性的时候,会触发setter回调。

script

data() {
    return {
      message:'',
      info: ''
    }
  },
  computed: {
    reversedMessage: {
      get() { // get回调
        return this.message.split('').reverse().join('')
      },
      set(newValue) { // set回调
        this.info = newValue
      }
    }
  },
  methods: {
    changeMessage(event) {
      // 修改reversedMessage计算属性
      this.reversedMessage = event.target.value;
    }
  }

react

react hooks使用useMemo表示memoized的值,使用useCallback表示memoized的回调函数,实现与vue中computed类似的功能。

适用场景:子组件使用了PureComponent或者React.memo,那么你可以考虑使用useMemouseCallback封装提供给他们的props,这样就能够充分利用这些组件的浅比较能力。

(1)useMemo

useMemo返回一个memoized的值。useMemo会依赖某些依赖值,只有在某个依赖项改变时才会重新计算memoized值。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。useMemo可以作为性能优化的手段。

传入useMemo的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect的适用范畴,而不是useMemo

function NewComponent(props) {
  const { num } = props;
  const [size, setSize] = useState(0);
  // max是useMemo返回的一个memoized的值
  const max = useMemo(() => Math.max(num, size), [num, size]);
  return (<div>
    <input
      type="number"
      value={size}
      onChange={(e) => setSize(e.target.value)} />
    <div>Max {max}</div>
  </div>);
}

(2)useCallback

useCallback把内联回调函数及依赖项数组作为参数传入useCallback,它将返回该回调函数的memoized版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如shouldComponentUpdate的子组件时,它将非常有用。

function NewComponent(props) {
  const [message, setMessage] = useState('hello world.');
  const handleChange = useCallback((value) => {
    setMessage(value);
  }, []);
  return (<div>
    <input
      type="number"
      value={message}
      onChange={(e) => handleChange(e.target.value)} />
    <div>{message}</div>
  </div>);
}

5.侦听器(watch vs getDerivedStateFromProps + componentDidUpdate)

侦听器是指通过监听props或者组件数据(datastate)的变化来执行一些异步或者数据操作。

vue

vue中主要通过watch监听propsdatacomputed(计算属性)的变化,执行异步或开销较大的操作。

下面ProjectManage组件通过watch监听projectId prop的变化获取对应的项目信息。

export default {
  name: "ProjectManage",
  props: ["projectId"],
  data() {
    return {
      projectInfo: null
    };
  },
  watch: {
    projectId(newVaue, oldValue) {
      if (newVaue !== oldValue) {
        this.getProject(newValue);
      }
    }
  },
  methods: {
    getProject(projectId) {
      projectApi
        .getProject(projectId)
        .then(res => {
          this.projectInfo = res.data;
        })
        .catch(err => {
          this.$message({
            type: "error",
            message: err.message
          });
        });
    }
  }
};

react

react中通过static getDerivedStateFromProps()componentDidUpdate()实现监听器的功能。

(1)static getDerivedStateFromProps()

getDerivedStateFromProps会在调用render方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新state,如果返回null则不更新任何内容。

关于getDerivedStateFromProps有2点说明:

  • 1)不管是props变化、执行setState或者forceUpdate操作都会在每次渲染前触发此方法。

  • 2)当state的值在任何时候都取决于props的时候适用该方法。

    class NewComponent extends React.Component { constructor(props) { super(props); this.state = { info: '' } } static getDerivedStateFromProps(nextProps, prevState) { // state中的info根据props中的info保持同步 if (nextProps.info !== prevState.info) { return { info: nextProps.info } } return null; } render() { const { info } = this.state; return <div>{info}</div> } }

(2)componentDidUpdate()

componentDidUpdate()方法在组件更新后被调用。首次渲染不会执行此方法。当组件更新后,可以在此处操作DOM、执行setState或者执行异步请求操作。

componentDidUpdate(prevProps, prevState, snapshot)

关于componentDidUpdate有4点说明:

  • 1)componentDidUpdate()的第三个参数snapshot参数来源于getSnapshotBeforeUpdate()生命周期的返回值。若没有实现getSnapshotBeforeUpdate(),此参数值为undefined
  • 2)可以在componentDidUpdate()中直接调用setState(),但是它必需被包裹在一个条件语句里,否则会导致死循环。
  • 3)可以在componentDidUpdate()对更新前后的props进行比较,执行异步操作。
  • 4)如果shouldComponentUpdate()返回值为false,则不会调用componentDidUpdate()

下面NewComponent组件在componentDidUpdate()里判断

class NewComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      projectInfo: null
    }
  }
  getProject = (projectId) => {
    projectApi
      .getProject(projectId)
      .then(res => {
        this.projectInfo = res.data;
      })
      .catch(err => {
        message.error(err.message);
      });
  }
  componentDidUpdate(prevProps) {
    if (this.props.projectId !== prevProps.projectId) {
      this.getProject(this.props.projectId);
    }
  }
  render() {
    const { projectInfo } = this.state;
    return <React.Fragment>
      <div>{projectInfo.name}</div>
      <div>{projectInfo.detail}</div>
      </React.Fragment>
  }
}

6.ref

ref用来给元素或子组件注册引用信息,允许我们访问子组件或者子节点。

ref常用于:

  • 管理焦点,文本选择或媒体播放。
  • 触发强制动画。

vue

通过给组件或者子元素设置ref这个attribute为子组件或者子元素赋予一个ID引用。

$refs只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问$refs

(1)子元素引用ref

子元素上引用ref

 <div>
    <input type="text" v-model="message" ref="inputMessage"  />
  </div>

加载完毕后使输入框自动获取焦点

mounted() {
  this.$refs.inputMessage.focus();
}

(2)子组件引用ref

子组件引用ref常用于父组件使用子组件的方法。

常用表单验证就是采用这种方式验证的。

<template>
  <div>
    <el-form ref="createForm" label-width="80px" :model="form" :rules="rules">
      <el-form-item label="名称" prop="name">
        <el-input v-model="form.name"></el-input>
      </el-form-item>
      <el-form-item label="邮箱" prop="email">
        <el-input v-model="form.email"></el-input>
      </el-form-item>
    </el-form>
    <el-button @click="handleSubmit">提交</el-button>
  </div>
</template>

<script>
export default {
  name: 'CreateForm',
  data() {
    return {
      form: {
        name: '',
        email: ''
      },
      rules: {
        name: [{required: true, message: '名称不能为空', trigger: 'blur'}],
        email: [
          { required: true, message: '请输入邮箱地址', trigger: 'blur' },
          { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
        ]
      }
    }
  },
  methods: {
    handleSubmit() {
      this.$refs.createForm.validate((valid) => {
        console.log(valid);
      })
    }
  }
}
</script>

react

react中不像vue中直接给ref传字符串类型值,class组件通过React.createRef绑定ref属性(React v16.3版本之后),函数组件通过useRef绑定ref属性,还可以使用React.forwardRef用于转发ref属性到子组件中。

(1)class组件绑定ref

通过React.createRef在构造函数中生成ref,在绑定到input元素上,加载完成后自动聚焦。

class NewComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      message: 'hello world'
    };
    this.inputRef = React.createRef();
  }
  componentDidMount() {
    this.inputRef.current.focus();
  }
  render() {
    const { message } = this.state;
    return (<div>
      <input
        type="number"
        ref={this.inputRef}
      />
      <div>{message}</div>
    </div>);
  }
}

(2)函数组件绑定ref

函数组件可以使用useRef绑定ref属性。useRef返回一个可变的ref对象,其 .current属性被初始化为传入的参数(initialValue)。返回的ref 对象在组件的整个生命周期内保持不变。

function NewComponent() {
  const [message, setMessage] = useState('hello world.');
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (<div>
    <input type="number" ref={inputRef} />
    <div>{message}</div>
  </div>);
}

(3)React.forwardRef 转发 ref 到子组件

React.forwardRef会创建一个React组件,这个组件能够将其接受的ref属性转发到其组件树下的另一个组件中。

这种技术并不常见,但在以下两种场景中特别有用:

  • 转发refsDOM组件
  • 在高阶组件中转发refs

父组件直接传递ref属性给子组件NewComponent

function Parent() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return <div>
     <NewComponent ref={inputRef}><h2>This is refs.</h2></NewComponent>
  </div>;
}

子组件使用React.forwardRef 接受渲染函数作为参数,父组件加载完成后聚焦到input输入框。

const NewComponent = React.forwardRef((props, ref) => (<div>
  <input type="number" ref={ref} />
  <div>{props.children}</div>
</div>));

7.表单(v-model vs value)

对于表单,vue中使用v-model在表单组件上实现双向数据绑定,react中通过在表单组件上绑定value属性以受控组件的形式管理表单数据。

vue

v-model指令在表单<input><textarea><select>元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。v-model本质上不过是语法糖。

v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • texttextarea元素使用value属性和input事件;
  • checkboxradio使用checked属性和change事件;
  • select字段将value作为prop并将change作为事件。

(1)基本用法

Ⅰ.文本

input输入框上绑定v-model属性绑定msg,当修改input输入值时,msg会自动同步为用户输入值。

 <div>
  <input v-model="msg" />
</div>

v-model写法等价于:value@input的结合,:value绑定输入值,@input表示接收输入事件修改msg的值为输入的值,从而实现双向绑定。

<div>
  <input :value="msg" @input="e => (msg = e.target.value)" />
</div>
Ⅱ.复选框

单个复选框绑定到布尔值

<div>
  <input type="checkbox" v-model="checked" />
  <label for="checkbox">{{ checked }}</label>
</div>

多个复选框绑定到数组

<div>
    <input type="checkbox" id="apple" value="apple" v-model="selectedFruits" />
    <label for="jack">apple</label>
    <input
      type="checkbox"
      id="banana"
      value="banana"
      v-model="selectedFruits"
    />
    <label for="john">banana</label>
    <input type="checkbox" id="mango" value="mango" v-model="selectedFruits" />
    <label for="mango">mango</label>
    <div>Selected fruits: {{ selectedFruits }}</div>
  </div>

data

 data() {
    return {
      selectedFruits: []
    };
  }
Ⅲ.选择框

1)选择框单选时,绑定字符串

<div>
  <select v-model="selected">
    <option
      v-for="option in options"
      :value="option.value"
      :key="option.value"
    >
      {{ option.text }}
    </option>
  </select>
  <div>Selected: {{ selected }}</div>
</div>

data

data() {
  return {
    selected: "A",
    options: [
      { text: "One", value: "A" },
      { text: "Two", value: "B" },
      { text: "Three", value: "C" }
    ]
  };
}

2)选择框多选时,绑定的是数组

<div>
  <select v-model="selected" multiple>
    <option
      v-for="option in options"
      :value="option.value"
      :key="option.value"
    >
      {{ option.text }}
    </option>
  </select>
  <div>Selected: {{ selected }}</div>
</div>

data

 data() {
  return {
    selected: ["A"], //多选时绑定的是数组
    options: [
      { text: "One", value: "A" },
      { text: "Two", value: "B" },
      { text: "Three", value: "C" }
    ]
  };
}

####(2)修饰符 vue对于v-model扩展了.lazy.number.trim修饰符增强功能。

Ⅰ.lazy

在默认情况下,v-model在每次input事件触发后将输入框的值与数据进行同步。添加了.lazy修饰符,转变为change事件进行同步。

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >
Ⅱ.number

.number修饰符可以自动将用户的输入转换为数值类型,尤其是处理数字类型表单项时尤其有用。

 <input type="number" v-model.number="num" />
Ⅲ.trim

.trim修饰符可以自动过滤用户输入的首尾空白字符。

 <input v-model.trim="msg" />

(3)自定义组件使用v-model

一个组件上的v-model默认会利用名为valueprop和名为 input的事件。

InputMessage绑定v-model值为msg

  <InputMessage v-model="msg" />

InputMessage组件通过value props接收值,emit input事件给父组件,修改父组件中msg的值。

<template>
  <input v-model="message" @input="$emit('input', $event.target.value)" />
</template>

<script>
export default {
  name: "InputMessage",
  props: ["value"],
  data() {
    return {
      message: this.value
    };
  }
};

(4)不能使用v-model管理的表单组件(file等)

对于像<input type="file" />这种类型的表单组件,不能使用v-model管理组件数据,可以通过refs管理表单组件的数据,这点和react中的非受控组件一致。

<template>
  <div>
    <input type="file" ref="file" @change="fileChange" />
  </div>
</template>

<script>
export default {
  name: "InputFile",
  data() {
    return {};
  },
  methods: {
    fileChange() {
      const file = this.$refs.file.files[0];
      console.log(file);
    }
  }
};
</script>

react

react中,表单元素(<input> <select> <checkbox>)通常维护自己的state,将state赋值给value属性,并根据用户输入通过setState()更新state,以这种方式控制的表单元素称为“受控组件”。

(1)受控组件

受控组件中,state作为组件“唯一的数据源”,组件还控制着用户操作过程中表单发生的操作。

class CreateForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: ''
    }
  }

  nameChange = (event) => { // 接收事件作为参数
   this.setState({
     name: event.target.value
   });
  }

  render() {
    const { name } = this.state;
    return (
    <div>
      <input value={name} onChange={this.nameChange} />
      <div> name: {name} </div>
    </div>)
  }
}

(2)非受控组件

在react中,对于不能使用state方式管理的表单组件称为非受控组件,非受控组件的值不能通过代码控制,表单数据交由DOM节点来处理。对于非受控组件,可以使用refDOM节点中获取表单数据。

<input type="file" />始终是一个非受控组件,通过创建ref的形式获取文件数据。

class CreateForm extends React.Component {
  constructor(props) {
    super(props);
    this.fileRef = React.createRef(null);
  }

  fileChange = (event) => {
    event.preventDefault();
    const file = this.fileRef.current.files[0];
    console.log(file)
  }

  render() {
    return (
    <div>
      <input type="file" ref={this.fileRef} onChange={this.fileChange} />
    </div>)
  }
}

8.插槽(slot vs Render Props+this.props.children)

vue和react中都实现了“插槽”(内容分发)功能,vue中主要通过slot实现插槽功能,react中通过this.props.childrenRender props实现类似vue中的插槽功能。

vue

vue中通过<slot>实现插槽功能,包含默认插槽、具名插槽、作用域插槽。

(1)默认插槽

默认插槽使用<slot></slot>在组件内预留分发内容的“占位”,在组件起始标签和结束标签可以包含任何代码,例如html或者其他组件。

关于默认插槽,有2点说明:

(1)如果不使用插槽,插入到组件中的内容不会渲染。 (2)插槽可以设置后备内容,在不插入任何内容时显示。

使用默认插槽的组件:

<template>
  <div>
    <h2>Slot:</h2>
     <slot>Default content.</slot> // 使用slot预留插槽占位,slot中的内容作为后备内容
  </div>
</template>

<script>
export default {
  name: "SlotComponent",
  data() {
    return {};
  }
};
</script>

父组件使用该组件,在组件起始标签和结束标签添加插槽内容。

<slot-component>
  <h2>This is slot component.</h2>
</slot-component>

最终插槽内容会被插入到组件<slot></slot>占位的位置。

  <div>
    <h2>Slot:</h2>
    <h2>This is slot component.</h2>
  </div>

<slot-component>没有添加插槽内容时,会渲染默认插槽内容。

<div>
    <h2>Slot:</h2>
    Default content.
  </div>

(2)具名插槽

默认插槽只能插入一个插槽,当插入多个插槽时需要使用具名插槽。slot元素有一个默认的attribute name,用来定义具名插槽。

默认插槽的namedefault

在向具名插槽提供内容的时候,我们可以在一个 <template>元素上使用v-slot指令,并以v-slot的参数的形式提供其名称,将内容插入到对应的插槽下。

v-slot:也可以简写为#,例如v-slot:footer可以被重写为#footer

插槽组件slot-componentheader footer两个具名插槽和一个默认插槽。

<template>
  <div>
    <header>
      <slot name="header">
        Header content.
      </slot>
    </header>
    <main>
      <slot>
        Main content.
      </slot>
    </main>
    <footer>
      <slot name="footer">
        Footer content.
      </slot>
    </footer>
  </div>
</template>

向插槽中分别插入内容:

<slot-component>
  <template v-slot:header>
    <div>This is header content.</div>
  </template>
  <template>
    <div>This is main content.</div>
  </template>
  <template #footer>
    <div>This is footer content.</div>
  </template>
</slot-component>

最终html会被渲染为:

<div>
  <header>
    <div>This is header content.</div>
  </header>
  <main>
    <div>This is main content.</div>
  </main>
  <footer>
    <div>This is footer content.</div>
  </footer>
</div>

(3)作用域插槽

有时候我们需要在父组件中显示插槽组件的数据内容,这时候作用域插槽就派上用场了。

作用域插槽需要在<slot>元素上绑定attribute,这被称为插槽prop。在父级作用域中,可以使用带值的v-slot来定义我们提供的插槽prop的名字。

插槽组件

<template>
  <div>
    <header>
      <slot name="header">
        Header content.
      </slot>
    </header>
    <main>
      <slot :person="person">
        {{ person.name }}
      </slot>
    </main>
  </div>
</template>

<script>
export default {
  name: "SlotComponent",
  data() {
    return {
      person: {
        name: "xiaoming",
        age: 14
      }
    };
  },
  methods: {}
};
</script>

父组件作用域将包含所有插槽prop的对象命名为slotProps,也可以使用任意你喜欢的名字。

 <slot-component>
  <template slot="header">
    <div>This is header content.</div>
  </template>
  // 使用带值的`v-slot`来定义插槽`prop`的名字
  <template v-slot:default="slotProps">
    <div>{{ slotProps.person.name }}</div>
    <div>{{ slotProps.person.age }}</div>
  </template>
</slot-component>

最终html将被渲染为:

<div>
  <header>
    <div>This is header content.</div>
  </header>
  <main>
    <div>xiaoming</div>
    <div>14</div>
  </main>
</div>

react

react中通过this.props.childrenRender props实现类似vue中的插槽功能。

(1)this.props.children

每个组件都可以通过this.props.children获取包含组件开始标签和结束标签之间的内容,这个与vue中的默认插槽类似。

class组件中使用this.props.children,在function组件中使用props.childrenclass组件使用this.props.children获取子元素内容。

class NewComponent extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <div>{this.props.children}</div>
  }
}

function组件使用props.children获取子元素内容。

function NewComponent(props) {
  return <div>>{props.children}</div>
}

父组件使用NewComponent组件

 <NewComponent>
   <h2>This is new component header.</h2>
   <div>
     This is new component content.
   </div>
 </NewComponent>

最终html将被渲染为:

<div>
  <h2>This is new component header.</h2>
  <div>This is new component content.</div>
</div>

(2)Render props

render prop是指一种在React组件之间使用一个值为函数的prop共享代码的技术。render prop是一个用于告知组件需要渲染什么内容的函数prop

比如我们常用的react-router-dom中的Routecomponent prop就采用了典型的render prop的用法。

<Router history={browserHistory}>
    <Route path="/" component={Index}> // component props接收具体的组件
      <IndexRoute component={HomePage} />
      <Route path="/users" component={Users} />
    </Route>
  </Router>

通过多个render prop即可实现类似vue中具名插槽的功能。

NewComponent定义了headermainfooter prop

class NewComponent extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const { header, main, footer, children } = this.props;
    return (<div>
              <header>
                {header || (<div>Header content.</div>)}
              </header>
              <main>
                {main || (<div>Main content.</div>)}
              </main>
              {children}
              <footer>
                {footer || (<div>Footer content.</div>)}
              </footer>
            </div>);
  }
}

父组件向子组件传递render prop

 <NewComponent
    header={<div>This is header content.</div>}
    content={<div>This is main content.</div>}
    footer={<div>This is footer content.</div>}>
    <div>
      This is new component children.
    </div>
</NewComponent>

最终html将被渲染为

<div>
  <header>
    <div>This is header content.</div>
  </header>
  <main>
    <div>This is main content.</div>
  </main>
  <div>This is new component children.</div>
  <footer>
    <div>This is footer content.</div>
  </footer>
</div>

评论 抢沙发

表情
  1. #1

    来自海南海口的用户 1天前
    开始学习

  2. #2

    来自北京朝阳的用户 4天前
    涨知识了

  3. #3

    来自山东济南的用户 6天前
    优秀,加油!

  4. #4

    来自天津南开的用户 8天前
    学习了, 总结的很细很棒, 赞

  5. #5

    来自福建福州的用户 8天前
    赞一个

  6. #6

    来自北京朝阳的用户 9天前
    感谢您的文章,又学到了不少东西

  7. #7

    来自天津南开的用户 11天前
    虽然很多还看不懂 但是很赞

  8. #8

    来自天津南开的用户 15天前
    这个要记录下来,每天打卡。跟着大佬学习

  9. #9

    来自上海徐汇的用户 18天前
    大神 听说你又有才 人又帅,明明可以靠脸吃饭,可是你偏要靠才华,鼓励师都被你撩了我们怎么办

  10. #10

    来自北京朝阳的用户 26天前
    楼下方便加下微信麽,交流下!