react 组件
组件间的传值
父传子:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Father extends Component {
constructor(props) {
super(props);
this.state = {
name: 'Tom'
};
}
render() {
return (
<div>
<h1>这是父组件</h1>
{/* 父传子:父组件通过子组件的props属性传值给子组件 */}
<Son name={this.state.name} />
</div>
);
}
}
class Son extends Component {
render() {
return (
<div>
<h3>这是子组件</h3>
<p>显示父组件传递过来的值:{this.props.name}</p>
</div>
);
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
子传父:
- 在父组件中定义方法,抛出形参;
- 给子组件绑定方法;
- 在子组件中用
this.props.方法名
调用 方法同时传入实参。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Father extends Component {
constructor(props) {
super(props);
this.state = {
name: 'Tom',
iNum: 0,
};
}
{/* 1.父组件定义一个传参的方法: */}
fnGetData = iNum => {
this.setState({
iNum
});
};
render() {
return (
<div>
<h1>这是父组件</h1>
<p>显示子组件传递过来的值:{this.state.iNum}</p>
{/* 2.将方法绑定在子组件身上: */}
<Son fnGetData={this.fnGetData} />
</div>
);
}
}
class Son extends Component {
render() {
return (
<div>
<h3>这是子组件</h3>
{/* 3.在子组件中调用这个方法,同时传一个值进去: */}
<input type="button" onClick={() => this.props.fnGetData(10)} />
</div>
);
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
小结:父子组件传值皆是用
this.props
接收。
同辈组件间的传值:
方法一 (较常用):利用父组件做中转,传值过程为:子组件1 → 父组件 → 子组件2
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class Father extends Component {
constructor(props) {
super(props);
this.state = {
name: 'Tom',
iNum: 0,
};
}
fnGetData = iNum => {
this.setState({
iNum
});
};
render() {
return (
<div>
<h1>这是父组件</h1>
{/* 2.将方法绑定在子组件身上: */}
<Son fnGetData={this.fnGetData} />
<Son2 iNum={this.state.iNum} />
</div>
);
}
}
class Son extends Component {
render() {
return (
<div>
<h3>这是子组件一</h3>
<input type="button" value="传值给父组件" onClick={() => this.props.fnGetData(10)} />
</div>
);
}
}
class Son2 extends Component {
render() {
return (
<div>
<h3>这是子组件二</h3>
<p>显示子组件一传递过来的值:{this.props.iNum}</p>
</div>
);
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
方法二 (不常用):实例化系统模块events中的EventEmitter类,通过emit发送数据,通过on接收数据
例子:子组件二 传值给 子组件一
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
// 1.导入events模块中的EventEmitter类:
import { EventEmitter } from 'events';
// 2.实例化一个EventEmitter对象:
let bus = new EventEmitter();
class Father extends Component {
render() {
return (
<div>
<h1>这是父组件</h1>
<Son01 />
<Son02 />
</div>
);
}
}
class Son01 extends Component {
constructor(props){
super(props);
this.state = {
msg:''
}
}
// 4.生命周期中监听,接收数据:
componentDidMount(){
bus.on('send',dat=>{
this.setState({
msg:dat.msg
})
})
}
render() {
return (
<div>
<h3>这是子组件一</h3>
<p>显示子组件二传递过来的值:{ this.state.msg }</p>
</div>
);
}
}
class Son02 extends Component {
// 3.通过实例化的bus对象的emit方法来发送数据,'send' 相当于是一个自定义事件名称
fnSend=()=>{
bus.emit('send',{
msg:'数据内容为: 我来自子组件二'
})
}
render() {
return (
<div>
<h3>这是子组件二</h3>
<input type="button" value="传值给子组件一" onClick={ this.fnSend } />
</div>
);
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
redux
类似于 Vue 中的 VueX。redux 使用时:
- 每个 redux 组件都需要导入 store。
- 想跟踪 store 的变化,需要订阅 store,且在组件销毁时要撤销订阅。
原理:
语法:
- 下载:
yarn add redux
- 新建
src/store/index.js
文件:(写法固定)
// 导入创建 store 的方法:
import { createStore } from 'redux';
// 导入仓库数据:
import reducer from './reducer';
// 创建 store:
let store = createStore(reducer);
// 导出 store:
export default store;
- 新建
src/store/reducer.js
文件:
// 数据中心的初始数据:
const oDefaultData= {
"aList":[],
"sTodo":""
};
// state 参数是仓库的数据;action 参数是由 store 传递进来的数据变更
let reducer = (state = oDefaultData, action)=>{
// 处理工单:
if (action.type === "add_goods") {
let aNewState = JSON.parse(JSON.stringify(state)); // 深拷贝
aNewState.push(action.value);
return aNewState;
}
return state;
}
export default reducer;
- 修改 store 中的数据:
// store.dispatch() 将数据变更传到数据中心
fnAdd=(e)=>{
store.dispatch({
type:'add_goods',
value:target.value
});
}
<Button onClick={() => this.fnAdd(item)}>加入购物车</Button>
- 在组件中使用:
// 导入store/index.js 文件:
import store from './store';
class TodoList extends Component{
constructor(props){
super(props);
// store.getState() 获取数据中心的数据:
this.state = store.getState();
}
}
例🌰子:
图示:
src/store/index.js
文件(写法固定):
// 导入创建store的方法
import { createStore } from 'redux';
// 导入数据仓库
import reducer from './reducer';
// 创建store
let store = createStore( reducer );
// 导出store
export default store;
- 新建
public/data.json
文件(模拟 axios 请求数据,实际项目中不用新建):
{
"aList":["学习html","学习css","学习javascript","学习jquery","学习vue"],
"sTodo":""
}
- 新建
src/store/actiontype.js
文件(能更好地纠错):
const CHANGE_VAL = 'change_val';
const ADD_LIST = 'add_list';
const DEL_LIST = 'del_list';
const INIT_DATA = 'init_data';
export { CHANGE_VAL, ADD_LIST, DEL_LIST, INIT_DATA }
src/store/reducer.js
文件:
import { CHANGE_VAL, ADD_LIST, DEL_LIST, INIT_DATA } from './actiontype';
let oDefaultData = {
aList:[],
sTodo:''
}
/**
* @description:数据仓库的函数
* @param {type} state 仓库的数据
* @param {type} action 由 store 传递进来的数据变更
* @return: 数据中心的最新值
*/
let reducer = (state = oDefaultData, action) => {
// 1、接收一个修改数据的工单
if(action.type === CHANGE_VAL){
// 将state深拷贝一份
let oNewState = JSON.parse( JSON.stringify( state ) );
oNewState.sTodo = action.value;
return oNewState;
}
// 2、接收一个增加数据的工单
if(action.type === ADD_LIST){
let oNewState = JSON.parse( JSON.stringify( state ) );
// 判断输入框的值是否为空或者纯空格
if(oNewState.sTodo.trim() === ''){
alert('请输入计划内容!');
// sTodo可能是纯空格,将它清空一下
oNewState.sTodo = '';
return oNewState;
}else{
// 将sTodo的值通过push方法放到数组中
oNewState.aList.push( oNewState.sTodo.trim() );
// 清空输入框的值
oNewState.sTodo = '';
return oNewState;
}
}
// 3、接收一个删除数据的工单
if (action.type === DEL_LIST) {
let oNewState = JSON.parse(JSON.stringify(state));
// 删除oNewState里面的数组的某个成员:
oNewState.aList.splice(action.value, 1);
return oNewState;
}
// 4、接收一个初始化数据的工单
if (action.type === INIT_DATA) {
// 直接返回传递过来的新对象,这个对象会替换数据中心的对象,从而作为数据中心最新的值
return action.value;
}
return state;
}
export default reducer
src/index.js
文件:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './main.css';
import axios from 'axios';
import store from './store'
import { CHANGE_VAL, ADD_LIST, DEL_LIST, INIT_DATA } from './store/actiontype';
class Todolist extends Component{
constructor(props){
super(props);
// 1.1、获取数据中心的数据:
this.state = store.getState();
// 1.2、订阅数据中心的修改
// subscribe 执行后有一个返回值(返回值是一个取消订阅的方法)
this.unsubscribe = store.subscribe(this.fnStoreChange)
}
// 1.3、每次数据修改时,重新去数据中心拿一次数据。然后设置到state中
fnStoreChange=()=>{
this.setState( store.getState() )
}
// 1.4、在组件销毁之前取消数据中心的定义,从而优化组件
componentWillUnmount() {
this.unsubscribe();
}
// 组件挂载到页面 → 请求数据→ 数据设置到数据中心
componentDidMount() {
// 模拟 axios 请求:
axios.get('/data.json').then(dat => {
store.dispatch({
type: INIT_DATA,
value: dat.data
})
})
}
// 2.1、创建工单(修改数据中心的数据):
fnChange=(e)=>{
let action = {
type: CHANGE_VAL,
value: e.target.value
}
// 2.2提交工单:
store.dispatch(action);
}
// 创建并提交工单(增加数据):
fnAdd=()=>{
store.dispatch({
type: ADD_LIST
});
}
// 创建并提交工单(删除数据):
fnDel = (i) => {
store.dispatch({
type: DEL_LIST,
value: i
});
}
render() {
let { aList, sTodo } = this.state;
return (
<div className="list_con">
<h2>To do list</h2>
<input type="text" value={sTodo} onChange={this.fnChange} className="inputtxt" />
<input type="button" name="" value="增加" id="btn1" className="inputbtn" onClick={this.fnAdd} />
<ul id="list" className="list">
{
aList.map((item, i) => (
<li key={i}><span>{item}</span><b className="del" onClick={() => this.fnDel(i)}>删除</b></li>
))
}
</ul>
</div>
)
}
}
ReactDOM.render(<Todolist />, document.getElementById('root'));
react-redux
react-redux 是对 redux 的优化。
语法:
- 下载:
yarn add react-redux
src/index.js
文件:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
src/app.js
文件:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
- 使用时:
import React from 'react';
// 导入 connect 高阶组件来连接数据中心:
import { connect } from 'react-redux';
const List = props => (
<div>
{
props.aList.map...
}
<button onClick={ () => { props.fnAddCart(item) }}>按钮</button>
</div>
);
// 映射一般的属性和方法到 props 属性上:
const mapStateToProps = (state) => { aList: state.aList }
// 映射操作 redux 的方法到props 属性上:
const mapDispathtchToProps = dispatch => { fnAddCart(goods){ dispatch({
type:'ADD_GOODS',
goods:goods
}) } }
// 导出 connet 方法的返回值,方法将组件名作为参数传入( mapDispatchToProps 没有定义时可以用 null 代替):
export default connect(mapStateToProps,mapDispatchToProps)(List);
组件的 children 属性
react 中组件的 children 属性类似于 vue 中的插槽。
- 组件以标签使用时,可以用单标签的方式,也可以用双标签的方式。
- 如果以双标签的方式使用:在双标签中插入内容,那么组件的 props 上就有了 children 属性,children 属性值就是组件标签中间的内容。
- children 属性和 props 其它属性一样,可以是文本、jsx 对象、函数或组件。
- 属性是一般的本文:
// 定义组件
class Child extends Component {
render(){
return (
<div>组件的 children 属性:{ this.props.children }</div>
);
}
}
// 使用组件
<Child>123</Child>
- 属性是函数:
// 定义组件
class Child extends Component {
render(){
return (
<p>组件的 children 属性</p>
<input type="button" onClick={ this.props.children } value="按钮" />
);
}
}
// 使用组件
<Child>{ ()=>{ alert('Hello World!') } }</Child>
组件功能的复用
如果多个组件的部分功能相同,我们可以将这些部分功能封装成一个组件来达到组件功能的复用。
方法一:render-props模式
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import cat from './cat.png';
class Mouse extends Component{
// 1.定义鼠标的位置:
constructor(props){
super(props);
this.state = {
x:0,
y:0
}
}
// 2.定义mousemove事件触发的fnGetMouse方法,将鼠标的坐标值设置到state中:
fnGetMouse=(e)=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
// 3.在window对象上绑定mousemove事件,发生事件时触发fnGetMouse方法:
componentDidMount(){
window.addEventListener('mousemove',this.fnGetMouse)
}
// 4.在组件销毁之前,解除组件内部的事件绑定来优化组件
componentWillUnmount(){
window.removeEventListener('mousemove',this.fnGetMouse)
}
render(){
// let {x,y} = this.state;
// return <p>鼠标的位置是:x:{x}---y:{y}</p>
// 返回一个组件props上面的方法的调用:
// return this.props.fnShow(this.state);
// 优化:
return this.props.children(this.state);
}
}
// 定义一个图片组件,调用Mouse组件实现图片跟随鼠标移动:
function Cat(props){
return <img src={ cat } alt="猫" style={{'position':'fixed','left':props.x,'top':props.y}} />
}
// 渲染的是Mouse组件,Mouse组件里面返回一个Cat组件,同时将Mouse组件的state传给Cat组件,Cat组件就获取了Mouse组件的鼠标的功能。这种用法叫做 render-props模式:
// ReactDOM.render(<Mouse fnShow={ oMouse=><Cat x={ oMouse.x } y={oMouse.y} /> } />, document.getElementById('root'));
// 优化:用children属性来实现render-props模式
ReactDOM.render(<Mouse>{ oMouse=><Cat { ...oMouse } /> }</Mouse>, document.getElementById('root'));
方法二:高阶组件(HOC)
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import cat from './cat.png';
// 定义一个高级组件(高级组件的名称习惯在前面加一个with)
function withMouse(Comp){
class Mouse extends Component{
constructor(props){
super(props);
this.state = {
x:0,
y:0
}
}
fnGetMouse=(e)=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
// 在组件挂载到页面之后,在window对象上绑定mousemove事件
// 关联一个方法,这个方法通过事件对象拿到鼠标的位置,同时设置到state中
componentDidMount(){
window.addEventListener('mousemove',this.fnGetMouse)
}
// 在组件销毁之前,解除组件内部的事件绑定来优化组件
componentWillUnmount(){
window.removeEventListener('mousemove',this.fnGetMouse)
}
render(){
return <Comp {...this.state} />
}
}
return Mouse;
}
// 定义一个显示一张图片的组件
function Cat(props){
return <img src={ cat } alt="猫" style={{'position':'fixed','left':props.x,'top':props.y}} />
}
// 使用高阶组件
let WithMouseCat = withMouse( Cat );
ReactDOM.render(<WithMouseCat />, document.getElementById('root'));
组件的更新机制
父组件向子组件传值,若传递的是相同的值子组件就没必要更新了。
我们可以想办法使父组件的执行不触发子组件的执行,从而阻止一些无意义的更新。
方法一、纯组件 PureComponent
使用:
- 导入
PureComponent
- 创建子组件时用
PureComponent
代替Component
import React,{ Component,PureComponent } from 'react';
import ReactDOM from 'react-dom';
class Father extends Component{
constructor(props){
super(props);
this.state = {
iNum:10
}
}
fnAdd=()=>{
this.setState(state=>({iNum:state.iNum+1}))
}
render(){
console.log('--父组件更新了--');
return (
<div>
<p>{ this.state.iNum }</p>
<input type="button" value="递增" onClick={ this.fnAdd } />
<Son />
</div>
)
}
}
// 使用纯组件来创建子组件,优化它的更新机制
class Son extends PureComponent{
render(){
console.log('--子组件更新了--');
return (
<div>
<p>这是子组件</p>
</div>
)
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
方法二、生命周期方法 shouldComponentUpdate
使用生命周期函数shouldComponentUpdate
,在生命周期函数中判断两个值是否相同。相同,则返回true
,组件更新;不同,则返回false
,组件不更新。
import React,{ Component } from 'react';
import ReactDOM from 'react-dom';
//let INUM03 = 10;
class Father extends Component{
constructor(props){
super(props);
this.state = {
iNum:0,
//aList:[{id:2015,value:1},{id:2016,value:2},{id:2017,value:3}]
}
this.iNum02 = 20;
}
fnRandom=()=>{
this.setState({
iNum:Math.floor(Math.random()*2)
})
}
render(){
console.log('--父组件更新了--');
return (
<div>
{/* <ul>{this.state.aList.map((item,i)=><li key={ item.id }>{ item.value }</li>)}</ul> */}
<p>{ this.state.iNum }</p>
<input type="button" value="生成0或者1" onClick={ this.fnRandom } />
<Son iNum={ this.state.iNum } />
</div>
)
}
}
class Son extends Component{
// shouldComponentUpdate方法中通过nextProps参数拿到props最新的值
// 通过this.props拿到props之前的值
shouldComponentUpdate(nextProps){
return this.props.iNum !== nextProps.iNum;
}
render(){
console.log('--子组件更新了--');
return (
<div>
<p>这是子组件</p>
<p>显示父组件传递的值:{ this.props.iNum }</p>
</div>
)
}
}
ReactDOM.render(<Father />, document.getElementById('root'));
组件性能的优化
1、尽量减少state
里面的变量,有些变量可以存储在组件属性this
上,或者存储在组件外面。
例如:
let INUM = 10; // 存储在组件的外面
class Father extends Component {
construtor(props){
super(props);
this.state = {
iNum: 0;
aList: [{id:1,value:1},{id:2,value:2}]
}
this.iNum2 = 20; // 存储在组件的属性上
}
}
2、可以使用纯组件PureComponent
或者shouldComponentUpdate
阻止不必要的更新。
3、列表渲染中,尽量使用不重复且不变的key
值来优化组件的diff
算法。
4、。。。。。。
你可能需要:vue 父子组件传值、vue路由传参(编程式导航)。