React教程学习
1、hello-react
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- 引入有顺序 -->
<!-- 引入核心库 -->
<script src="../js/react.development.js"></script>
<!-- 引入dom库,用于操作dom -->
<script src="../js//react-dom.development.js"></script>
<!-- 引入babel,将jsx转为js -->
<script src="../js//babel.min.js"></script>
<script type="text/babel">/*此处一定要写babel*/
// 1、创建虚拟DOM
const VDOM = <h1>hello, React</h1>; // 此处一定不要写字符串,因为不是字符串
// 2、渲染虚拟DOM到页面
ReactDOM.render(VDOM, document.getElementById('test'));
</script>
2、创建虚拟DOM的方式
-
jsx
<script type="text/babel"> const VDOM = ( <h1 id="title">hello jsx</h1> ); ReactDOM.render(VDOM, document.getElementById('test1')); </script>
-
原生js
使用React自带的创建元素的方法React.createElement(标签名, 标签属性, 标签内容);
const VDOM2 = React.createElement('h1', {id: 'title2', className:'title2'}, 'hello-React');//标签名、标签属性、标签内容 ReactDOM.render(VDOM2, document.getElementById('test2'));
3、关于虚拟DOM
- 本质是Object类型的对象(一般对象)
- 虚拟DOM属性比较少,真实DOM属性多,因为虚拟DOM在React内部使用,无需很多属性
- 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
4、jsx语法规则
-
定义虚拟DOM时,不要写引号
-
注释render中标签时,使用**{/*
>*/}** -
标签中混入js表达式时,使用{}
-
样式的类名指定时不要用class,要用className(与ES6中的class保留字区分)
-
内联样式要用style={{key:value}}的形式去写,且key用驼峰形式
-
只能有一个根标签
-
标签必须闭合
-
标签首字母
- 若小写字母开头,则将标签转为html同名元素,若无该标签则报错
- 若大写字母开头,react就去渲染对应组件,若组件没有定义则报错
-
遍历数组数据
-
数据遍历只能用数组,不能用对象
-
一定要用对象的话用Object.keys()获取key数组,再获取value 例如:data[key]
-
<script type="text/babel"> const data = ['angular', 'react', 'vue']; const VDOM = ( <div> <h1>前端jsx练习</h1> <ul> { data.map((item, index) => { return <li key={index}>{item}</li> }) } </ul> </div> ); ReactDOM.render(VDOM, document.getElementById('test')); </script>
-
5、React组件开发
函数式组件
-
函数:首字母大写
-
虚拟dom挂载:使用标签
-
this指向:undefined。因为babel编译后开启了严格模式,禁止this指向Window
-
// 创建函数式组件 //首字母大写 function Demo() { console.log(this); //undefined,因为babel编译后开启了严格模式,禁止this指向Window return <h2>函数式组件(适用于简单组件)</h2> } //使用标签 ReactDOM.render(<Demo />, document.getElementById('test')); /** * 执行reactdom.render(<Demo />)发生过程 * 1、react解析组件标签,找到Demo组件 * 2、发现组件是函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现 */
-
解析过程
- react解析组件标签,找到Demo组件
- 发现组件是函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现
类式组件
-
类:首字母大写
-
虚拟dom挂载:使用标签
-
render中的this:指向类式组件的实例对象
-
// 创建类式组件 class Demo extends React.Component { render() { //render添加到实例对象的原型对象上,供实例使用 //render中的this,指向对象的实例对象 return ( <h2>类式组件(用于创建复杂组件)</h2> ); }; } // 渲染组件 ReactDOM.render(<Demo />, document.getElementById('test')); /** * 执行reactdom.render(<Demo />)发生过程 * 1、react解析组件标签,找到Demo组件 * 2、发现组件是类定义的,随后new出该实例,并通过该实例调用原型上的render方法 * 3、将render返回的虚拟dom转为真实dom */
6、组件实例的三大核心属性
state
区分简单组件/复杂组件
-
通过组件状态state
- 无state:简单
- 有state:复杂---------类式组件实例对象有state属性
-
初始化state
-
使用constructor
-
class Weather extends React.Component { constructor(props) { super(props); //初始化状态 this.state = {flag: false}; }; render() { console.log(this); return ( <h1>今天天气{this.state.flag? '炎热' : '凉爽'}</h1> ); }; };
-
获取state的值:this.state.—
-
修改state的值--------this.setState({flag: !this.state.flag});
-
严重注意,状态必须通过setState进行更新,且是合并,类似于小程序的setData
-
state简写
-
state数据
class Weather extends React.Component { state = {flag: false}; }
-
方法
-
自定义方法---------赋值语句+箭头函数
class Weather extends React.Component { changeWeather = () =>{ let flag = this.state.flag; this.setState({flag: !flag}); }; }
-
-
-
简单给元素绑定事件
-
this.changeWeather = this.changeWeather.bind(this);
-
今天天气{this.state.flag? ‘炎热’ : ‘凉 爽’}
-
constructor(props) { super(props); //初始化状态 this.state = {flag: false}; this.changeWeather = this.changeWeather.bind(this);// }; render() { console.log(this); return ( <h1 onClick={this.changeWeather}>今天天气{this.state.flag? '炎热' : '凉爽'}</h1> ); }; /** * 事件 */ changeWeather() { //由于changeWeahter是作为onclick的回调,使用不是通过实例调用的,是直接调用 //类中的方法默认开启了局部的严格模式,所以changeWeather的this为undefined //console.log(this.state.flag); let flag = this.state.flag; this.setState({flag: !flag}); };
-
props
-
只读属性,不允许修改
-
基本使用
-
绑定虚拟DOM时给传参
- ReactDOM.render(, document.getElementById(‘test’));
-
使用时使用this.props.name
-
render() { return ( <h2>{this.props.name}</h2> ); };
-
-
-
批量传递数据
-
使用展开运算符
const p = {name: 'tom', age: 18}; ReactDOM.render(<Person {...p}/>, document.getElementById('test'));
-
-
对数据进行限制
引入proptype.js文件
<script src="../js//prop-types.js"></script>
再为组件添加propTypes属性
- 类型:
- string: 类型
- bool:布尔
- func:函数
- number:数字
- array:数组
- object:对象
- symbol:symbol
- isRequired:必传
Person.propTypes = { name: PropTypes.string.isRequired };
- 类型:
-
对数据约定默认值
Person.defaultProps = { sex: '不男不女' };
-
props简写
class Person extends React.Component { static propTypes = {}; static defaultProps = {}; render() {}; }
-
构造器中的props:构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
-
函数式组件的props
-
使用函数的参数
function Person(props) { return { <ul> <li>props.name</li> </ul> }; }; //添加约束 Person.propTypes = { name: PropTypes.string }; Person.defualtProps = {};
-
ref
字符串ref
-
使用:在虚拟dom的标签中添加ref属性
<input ref="input1" type="text" placeholder="点击按钮提示数据" />
-
获取:使用函数时调用this.refs.input1获取标签,同getelementbyid,和vue一样
showData = () => { console.log(this.refs.input1.value); }
回调ref
- 内联回调
//在组件实例中添加input1属性,即该input,用this.input1调用
<input ref={cuurentNode => this.input1 = cuurentNode} type="text" placeholder="点击按钮提示数据" />
-
如果使用内联回调函数,当更新视图时会执行两次回调,第一次为null,第二次为标签
解决方法: 在组件实例中添加回调函数,再在ref调用
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据" />
createRef
使用react自带的api创建一个ref
class Demo extends React.Component {
myRef = React.createRef();
render() {
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
};
}
获取标签:this.myRef.current
事件处理
-
(1).通过onXxx属性指定事件处理函数(注意大小写)
- a.React使用的是自定义函数,------------为了更好的兼容性
- b.React的事件是通过事件委托方式处理的(委托给组件最外层的元素)---------为了高效
-
(2).通过event.target得到发生事件的DOM元素对象----------不要过度使用ref
7、获取表单数据
非受控组件
-
特点:标签属性值value现用现取,
handleSubmit = (event) => { event.preventDefault(); let {username, password} = this; console.log(username.value, password.value); }; render() { return ( <form action="http://www.baidu.com" onSubmit={this.handleSubmit}> 用户:<input ref={c => this.username = c} type="text" name="username"/><br /> 密码:<input ref={c => this.password = c} type="password" name="password" /><br /> <button>登录</button> </form> ); };
受控组件
- 用state记录数据,维护状态
state = {
username: '',
password: ''
};
handleSubmit = (event) => {
event.preventDefault();
let {username, password} = this;
console.log(username.value, password.value);
};
userChange = (event) => {
React.setState({username: event.target.value});
};
psdChange = (event) => {
React.setState({password: event.target.value});
}
render() {
return (
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
用户:<input onChange={this.userChange} type="text" name="username"/><br />
密码:<input onChange={this.psdChange} type="password" name="password" /><br />
<button>登录</button>
</form>
);
};
-
输入处理函数合并
dataChange = (dataType) => { return (event) => { this.setState({[dataType]: event.target.value}); }; }; render() { return ( <form action="http://www.baidu.com" onSubmit={this.handleSubmit}> 用户:<input onChange={this.dataChange('username')} type="text" name="username" /><br /> 密码:<input onChange={this.dataChange('password')} type="password" name="password" /><br /> <button>登录</button> </form> ); };
8、高阶函数
定义
- 若A函数,接收的参数是一个函数,即为高阶函数(Promise、setTimeout)
- 若A函数,调用的返回值依然是函数,即为高阶函数
函数的柯里化
定义
通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
9、组件的生命周期
卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById(‘test’));
旧生命周期
组件挂载生命周期-render
// 构造器
constructor(props) {
console.log('Count-constructor');
super(props);
};
// 组件将要挂载时
componentWillMount() {
console.log('Count-componentWillMount');
};
// 挂载dom
render() {
console.log('Count-render');
};
// 组件已经挂载完成
componentDidMount() {
console.log('Count-componentDidMount');
};
// 组件将要卸载
componentWillUnmount() {
console.log('Count-componentWillUnmount');
};
组件更新生命周期-setState
// 控制组件更新的‘阀门’
shouldComponentUpdate(nextProps, nextState) {
console.log('Count-shouldComponentUpdate');
return true;
};
// 组件将要更新
componentWillUpdate() {
console.log('Count-componentWillUpdate');
};
// 挂载dom
render() {
console.log('Count-render');
};
// 组件完成更新
componentDidUpdate() {
console.log('Count-componentDidUpdate');
};
组件更新生命周期-forceUpdate
- (不需要更改state,强制更新)
// 组件将要更新
componentWillUpdate() {
console.log('Count-componentWillUpdate');
};
// 挂载dom
render() {
console.log('Count-render');
};
// 组件完成更新
componentDidUpdate() {
console.log('Count-componentDidUpdate');
};
组件更新生命周期-父组件render
- 注意:receive函数是指父组件重新传递新参数,即第一次传递的参数无法调用该函数
// 子组件即将传参
componentWillReceiveProps(props) {
console.log('B-componentWillReceiveProps', props);
};
// 控制组件更新的‘阀门’
shouldComponentUpdate() {
console.log('Count-shouldComponentUpdate');
return true;
};
// 组件将要更新
componentWillUpdate() {
console.log('Count-componentWillUpdate');
};
// 挂载dom
render() {
console.log('Count-render');
};
// 组件完成更新
componentDidUpdate(preProps, preState, snapValue) {
console.log('Count-componentDidUpdate');
};
总结
初始化阶段
由ReactDOM.render()触发—初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount()===============>常用
- 可以初始化一些数据,以及异步请求、订阅消息,请求成功后会调用render,
更新阶段
由组件内部this.setState或this.forceUpdate或父组件render触发
- componentWillReceiveProps(props)–父组件render触发,可接收props参数
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
- 在该方法中可以进行网络请求,调用setState,但是需要包裹一层条件判断,比较prevProps与this.prop中属性值或者prevState与this.state中属性值是否变化,未变化则不执行,否则会导致死循环。
卸载组件
由ReactDOM.unmountComponentAtNode(组件)触发
- componentWillUnmount() =================>常用
- 收尾工作,关闭定时器,取消订阅消息
新生命周期
与旧的区别
- componentWillMount、componentWillUpdate、componentWillReceiveProps前加UNSAFE_
- 废除了componentWillMount、componentWillUpdate、componentWillReceiveProps钩子,增加了getDerivedStateFromProps和getSnapshotBeforeUpdate
新钩子
-
getDerivedStateFromProps少用,了解即可
//state的值在任何时候都取决于props static getDerivedStateFromProps(props, state) { console.log('Count-getDerivedStateFromProps'); retrun props; };
-
getSnapshotBeforeUpdate
// 生成数据快照 getSnapshotBeforeUpdate() { console.log('Count-getSnapshotBeforeUpdate'); //snapValue,数据快照 reuturn snapValue; };