1. hello React
React是Facebook出品的构建用户界面的js库,英文文档 https://reactjs.org/ 官方中文文档 https://react.docschina.org/
React的简单使用:
- 准备一个容器 如:
引入react的核心库,引入react-dom,用于支持react操作DOM,引入babel用于将jsx转换程js
<!-- 引入react核心库 --> <script type="text/javascript" src="../js/react.development.js"></script> <!-- 引入react-dom,用于支持react操作DOM --> <script type="text/javascript" src="../js/react-dom.development.js"></script> <!-- 引入babel,用于将jsx转为js --> <script type="text/javascript" src="../js/babel.min.js"></script>
创建虚拟DOM,渲染虚拟DOM到页面
/* 此处一定要写babel,表示里面写的是jsx语法 */ <script type="text/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" > /* 此处一定要写babel,表示写的内容需要babel编译 */
//1.创建虚拟DOM
const VDOM = ( /* 此处一定不要写引号,因为不是字符串 */
<h1 id="title">
<span>Hello,React</span>
</h1>
)
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
第二种使用js创建:
/*因为我们使用js就不需要引入babel来进行编译了*/
<script type="text/javascript" >
//1.创建虚拟DOM
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
当然可以看出来用js来写虚拟dom,当遇到嵌套的标签就会一直嵌套的写,所以用jsx方便些 ,其实js的写法就是jsx经过babel编译出来的结构写法。
关于虚拟DOM与真实的DOM:
- 虚拟DOM本质上是object对象
- 虚拟DOM本质上比较“轻”,真实DOM‘重’,虚拟DOM是react内部使用,没有真实DOM那么多的属性
- 虚拟DOM最终会被react转变为真实的DOM,呈现在页面上
3. jsx的语法规则
jsx是js的一种语法扩展,规则如下:
- 定义虚拟DOM时不需要写引号
- 标签中混入JS表达式时要用{}
- 样式的类名指定不要用class,要用className
- 内联样式,要用style={{key:value}}的形式去写
- 只能有一个根标签
- 标签必须闭合
标签首字母
(1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错
(2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
4. react中定义组件
函数式组件
<script type="text/babel">
//1.创建函数式组件
function MyComponent(){
console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
</script>
类式组件
<script type="text/babel">
//1.创建类式组件
class MyComponent extends React.Component {
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
*/
</script>
5. 组件实例三大属性
state
<script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
//1.创建类组件,必须要继承react内置的类
class MyComponent extends React.Component {
//构造器调用几次---1次
constructor(props) {
console.log('constructor');
super(props);
//初始化状态
this.state = {isHight:false}
//解决demo中this指向问题
this.demo = this.change.bind(this);
}
//render调用几次---1+n次,1是初始化那次,n是状态更新的次数
render() {//render是放在类的原型对象上,供实例使用
//render中的this指向---MyComponent的实例对象
console.log('render');
const {isHight} = this.state;
return <h2 onClick={this.demo}>你{isHight ? '身体一定很好' : '是个LSP'}</h2>
}
change() {//change调用几次---点几次调用几次
//注意demo作为onclick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了严格模式,所以demo中的this就指向undefined了
//this.state.isHight = !this.state.isHight;
//state中的状态不可直接更改,要借助内置的一个api进行更改
console.log(this);
const {isHight} = this.state;
this.setState({//是一种合并方式的更新
isHight:!isHight
});
}
}
//2.渲染组件到界面
ReactDOM.render(<MyComponent/>,document.getElementById('test'));
/* function demo() {
console.log('点击了');
} */
</script>
上述方式要解决this的指向问题写法有些繁琐,以下为state的简写方式
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//初始化状态,直接在类中写这种赋值语句表示给Weather的实例对象添加一个属性,名为state,值为一个对象
state = {isHot:false,wind:'微风'}
render(){
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//自定义方法————要用赋值语句的形式+箭头函数,箭头函数的this为上级作用域中的this刚好解决了函数中的this问题
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>
props
基本使用
<script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js class Person extends React.Component { render() { const {name, age, sex} = this.props; return ( <ul> <li>{name}</li> <li>{age}</li> <li>{sex}</li> </ul> ) } } ReactDOM.render(<Person name="jeke" age="19" sex="男" />, document.getElementById('test')); ReactDOM.render(<Person name="kak" age="23" sex="女" />, document.getElementById('test1')); ReactDOM.render(<Person name="tom" age="14" sex="男" />, document.getElementById('test2')); //如果要传递很多属性这样写太过繁琐,可以使用对象解构写法 //const p = {name:'老刘', age: 15, sex:'无'} //ReactDOM.render(<Person {...p} />, document.getElementById('test1')); </script>
页面:
对props传入的参数进行限制
//对props的限制操作需要引入一个新的库 <script src="../js/prop-types.js"></script> <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js class Person extends React.Component { constructor(props) { //构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props super(props); } render() { const {name, age, sex, speak} = this.props; //this.props.name = "xxx";//这行代码会报错,因为props只读 speak(); return ( <ul> <li>{name}</li> <li>{age}</li> <li>{sex}</li> </ul> ) } static propTypes = {//对传入的属性进行限制 name: PropTypes.string.isRequired,//表示必须传入一个字符串 sex: PropTypes.string.isRequired, age: PropTypes.number, speak: PropTypes.func//这里要设置函数类型必须这样写 } static defaultProps = {//对传入的数据默认值进行设置 age: 17, sex: '你是啥子', speak() { console.log('我是默认的'); } } } const p = {name:'老刘', age: 15, sex:'无'} function speak() { console.log('我说话了') } ReactDOM.render(<Person name="jeke" age={19} speak={speak} />, document.getElementById('test')); ReactDOM.render(<Person {...p} />, document.getElementById('test1')); </script>
props在函数中的使用
//对于函数式组件我们只能使用props这个属性了,而且对于props的限制也只能写在外面,因为没有static这个关键字 function Student(props) { const {name, age, sex, speak} = props; return ( <ul> <li>{name}</li> <li>{age}</li> <li>{sex}</li> </ul> ) } Student.propTypes = {//对传入的属性进行限制 name: PropTypes.string.isRequired,//表示必须传入一个字符串 sex: PropTypes.string.isRequired, age: PropTypes.number, speak: PropTypes.func//这里要设置函数类型必须这样写 } Student.defaultProps = {//对传入的数据默认值进行设置 age: 17, sex: '你是啥子', speak() { console.log('我是默认的'); } } const p = {name:'老刘', age: 15, sex:'无'} function speak() { console.log('我说话了') } ReactDOM.render(<Student name="jeke" age={19} />, document.getElementById('test1'));
ref
字符串形式的写法(已过时)
<script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js class Person extends React.Component { showData = () => { console.log(this.refs.input1.value); //这种形式操作ref现在已经有点过时不推荐使用 } showData2 = () => { console.log(this.refs.input2.value); } render(){ return ( <div> <input ref="input1" type="text" placeholder="点击按钮提示" /> <button onClick={this.showData}>点击以下提示</button> <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示" /> </div> ) } } ReactDOM.render(<Person />, document.getElementById('test')); </script>
回调形式(常用)
<script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js class Person extends React.Component { showData = () => { console.log(this.input1.value); } showData2 = () => { console.log(this.input2.value); } render(){ return ( <div> <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示" /> <button onClick={this.showData}>点击以下提示</button> <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点按钮提示" /> </div> ) } } ReactDOM.render(<Person />, document.getElementById('test')); </script>
回调形式中ref内联写法函数调用次数问题,ref的回调函数执行几次??? 第一次启动时执行一次之后更新render后就会执行两次,可以将内联写法改为外部调用形式来避免这种多次调用,当然本身这个问题没啥影响。
createRef写法(常用)
<script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js class Person extends React.Component { /* React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点 注意该方法是"专人专用"的,只能存一个,多个ref的话就只能不同命名来创建多个容器 */ myRef = React.createRef(); state = { isHot:true } showInfo = () => { console.log(this.myRef.current.value); } change = () => { const {isHot} = this.state; this.setState({ isHot:!isHot }); } saveInput = (c) => { this.input1 = c; console.log('@',c); } render() { const {isHot} = this.state; return ( <div> <h1 onClick={this.change}>今天很{isHot ? '炎热' : '凉爽'}</h1> <input ref={this.myRef} type="text" /> <button onClick={this.showInfo}>点击提示数据</button> </div> ) } } ReactDOM.render(<Person />, document.getElementById('test')); </script>
6. 事件处理
<script type="text/babel">
//创建组件
class Demo extends React.Component{
/*
(1).通过onXxx属性指定事件处理函数(注意大小写)
a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
(2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
*/
//创建ref容器
myRef = React.createRef()
myRef2 = React.createRef()
//展示左侧输入框的数据
showData = (event)=>{
console.log(event.target);
alert(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 = (event)=>{
alert(event.target.value);
}
render(){
return(
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
</script>
7. 高阶函数柯里化
<script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
//#region
/*
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
常见的高阶函数有:Promise、setTimeout、arr.map()等等
函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
function sum(a){
return(b)=>{
return (c)=>{
return a+b+c
}
}
}
*/
//#endregion
class Login extends React.Component {
//初始化状态
state = {
username: '张三',
password: ''
}
handleSubmit = () => {
event.preventDefault();//阻止默认事件
const {username, password} = this.state;
alert(`用户名:${username},密码:${password}`);
}
//相同的操作尽量统一一个函数来操作
//保存用户名到状态中
saveFormdata = (dataType) => {
return (event) => {
this.setState({[dataType]: event.target.value})
}
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveFormdata('username')} type="用户名" name="username" />
密码:<input onChange={this.saveFormdata('password')} type="password" name="password" />
<button>登录</button>
</form>
);
}
}
ReactDOM.render(<Login/>, document.getElementById('test'));
</script>
8. 组件的生命周期
- 旧版的生命周期
<script type="text/babel">
/*
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. componentWillMount()
3. render()
4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件render触发
1. shouldComponentUpdate()
2. componentWillUpdate()
3. render() =====> 必须使用的一个
4. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
*/
//创建组件
class Count extends React.Component{
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//组件将要挂载的钩子
componentWillMount(){
console.log('Count---componentWillMount');
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){//返回true才会更新
console.log('Count---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('Count---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('Count---componentDidUpdate');
}
render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
<A />
</div>
)
}
}
//父组件A
class A extends React.Component{
//初始化状态
state = {carName:'奔驰'}
changeCar = ()=>{
this.setState({carName:'奥拓'})
}
render(){
return(
<div>
<div>我是A组件</div>
<button onClick={this.changeCar}>换车</button>
<B carName={this.state.carName}/>
</div>
)
}
}
//子组件B
class B extends React.Component{
//组件将要接收新的props的钩子
componentWillReceiveProps(props){//初始化render时不执行
console.log('B---componentWillReceiveProps',props);
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('B---shouldComponentUpdate');
return true
}
//组件将要更新的钩子
componentWillUpdate(){
console.log('B---componentWillUpdate');
}
//组件更新完毕的钩子
componentDidUpdate(){
console.log('B---componentDidUpdate');
}
render(){
console.log('B---render');
return(
<div>我是B组件,接收到的车是:{this.props.carName}</div>
)
}
}
//渲染组件
ReactDOM.render(<Count/>,document.getElementById('test'))
</script>
初始状态:
父组件修改自己的state后(点击 点我+1)
- 新版生命周期
<script type="text/babel">
//创建组件
class Count extends React.Component{
/*
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1. constructor()
2. getDerivedStateFromProps
3. render()
4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1. getDerivedStateFromProps
2. shouldComponentUpdate()
3. render()
4. getSnapshotBeforeUpdate
5. componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
*/
//构造器
constructor(props){
console.log('Count---constructor');
super(props)
//初始化状态
this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
//获取原状态
const {count} = this.state
//更新状态
this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
this.forceUpdate()
}
//从props中得到派生的状态,若state的值都取决于props就可以使用这个周期函数了
static getDerivedStateFromProps(props,state){
console.log('getDerivedStateFromProps',props,state);
return null
}
//在更新之前获取快照
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return 'atguigu'
}
//组件挂载完毕的钩子
componentDidMount(){
console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(){
console.log('Count---shouldComponentUpdate');
return true
}
//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}
render(){
console.log('Count---render');
const {count} = this.state
return(
<div>
<h2>当前求和为:{count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.death}>卸载组件</button>
<button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
</div>
)
}
}
//渲染组件
ReactDOM.render(<Count count={199}/>,document.getElementById('test'))
</script>
getSnapshotBeforeUpdate的使用案例
做一个小效果,一个不断增加条数的新闻列表,要求滚动条滑倒那里的内容不会因为内容高度增加而被压下去
<script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
class NewsList extends React.Component {
state = {
newsArr: []
}
//组件加载完后
componentDidMount() {
setInterval(() => {
const {newsArr} = this.state;
const news = '新闻' + (newsArr.length + 1);//获取新闻序号加1
this.setState({newsArr: [news, ... newsArr]});//添加到状态中去
}, 1000);
}
getSnapshotBeforeUpdate() {
return this.list.scrollHeight;//获取改变前的滚动条高度
}
componentDidUpdate(preProps, preState, snapshotValue) {
this.list.scrollTop += this.list.scrollHeight - snapshotValue;//修改滚动条距离顶部的高度来抵消文字被顶上去
}
render() {
return (
<div ref={c => this.list = c} className="list">
{
this.state.newsArr.map((item, index) => {
return <div key={index} className="news" >{item}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewsList />, document.getElementById('test'));
</script>