什么是React ?
用于构建用户的JavaScript库
React的开发依赖
开发react必须依赖三个库
- react:包含react所必须的核心代码
- react-dom :react渲染在不同平台所需要的核心代码
- babel:将jsx转换成React代码的工具
◼第一次接触React会被它繁琐的依赖搞蒙,居然依赖这么多东西: (直接放弃?)
对于Vue来说,我们只是依赖一个vue.js文件即可,但是react居然要依赖三个包。
其实呢,这三个库是各司其职的,目的就是让每一个库只单纯做自己的事情;
在React的0.14版本之前是没有react-dom这个概念的,所有功能都包含在react里;
◼ 为什么要进行拆分呢?原因就是react-native。
react包中包含了react web和react-native所共同拥有的核心代码。
react-dom针对web和native所完成的事情不同:
✓ web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
✓ native端:react-dom会将jsx最终渲染成原生的控件(比如Android中的Button,iOS中的UIButton)。
◼ babel是什么呢?
Babel ,又名 Babel.js。
是目前前端使用非常广泛的编译器、转移器。
比如当下很多浏览器并不支持ES6的语法,但是确实ES6的语法非常的简洁和方便,我们开发时希望使用它。
那么编写源码时我们就可以使用ES6来编写,之后通过Babel工具,将ES6转成大多数浏览器都支持的ES5的语法。
◼ React和Babel的关系:
默认情况下开发React其实可以不使用babel。
但是前提是我们自己使用 React.createElement 来编写源代码,它编写的代码非常的繁琐和可读性差。
那么我们就可以直接编写jsx(JavaScript XML)的语法,并且让babel帮助我们转换成React.createElement。
后续还会详细讲到;
Hello React案例
ReactDOM.createRoot函数:用于创建一个React根,之后渲染的内容会包含在这个跟当中
app.render函数:
参数:要渲染的根组件
我们可以通过{}语法来引入外部的变量与表达式
注意:编写React的JavaScript代码时必须添加type=“text/babel”,作用是让babel解析为jsx语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<div id="app"></div>
<!-- 添加依赖 -->
<!-- 依赖三个包 1.react 2.react-dom:react渲染在不同平台需要的代码 3.babel将jsx转换为react代码的工具-->
<!-- cdn引入 -->
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<!-- babel -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">
// 编写React代码(jxs语法)
// babel jxs语法 -> 普通的JavaScript代码
//渲染Hello World
//react18以前 ReactDOM.render
// ReactDOM.render(<h2> Hello World</h2>,document.querySelector('#root'))
//react18以后
// const root = ReactDOM.createRoot(document.querySelector('#root'))
// root.render(<h2> Hello World</h2>)
const app = ReactDOM.createRoot(document.querySelector('#app'))
// 1.将文本定义为变量
let message = 'Hello World'
// 2.监听按钮的点击
function btnClick(){
// 1.修改数据
message = 'Hello 崔陈志'
// 2.重新渲染
app.render((
<div>
<h2>{message}</h2>
<button onClick={btnClick}>修改文本</button>
</div>
))
// console.log("btnClick");
}
app.render((
<div>
<h2>{message}</h2>
<button onClick={btnClick}>修改文本</button>
</div>
))
</script>
</body>
</html>
组件化开发
root.render 参数可以是一个HTML元素或者是一个组件
所以我们可以将之前的业务封装到一个组件当中,然后传入到ReactDOM.render函数中的第一个参数;
◼ 在React中,如何封装一个组件呢?这里我们暂时使用类的方式封装组件:
1.定义一个类(类名大写,组件的名称是必须大写的,小写会被认为是HTML元素),继承自React.Component
2.实现当前组件的render函数
✓ render当中返回的jsx内容,就是之后React会帮助我们渲染的内容
组件化数据依赖
◼ 组件化问题一:数据在哪里定义?
◼ 在组件中的数据,我们可以分成两类:
参与界面更新的数据:当数据变量时,需要更新组件渲染的内容;
不参与界面更新的数据:当数据变量时,不需要更新将组建渲染的内容;
◼ 参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中
我们可以通过在构造函数中 this.state = {定义的数据}
当我们的数据发生变化时,我们可以调用 this.setState 来更新数据,并且通知React进行update操作;
✓ 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面
this默认绑定指向window 但在严格模式下指向undefined (类里面的函数都是开启了严格模式的) babel会将代码开启严格模式
组件化事件绑定
◼ 组件化问题二:事件绑定中的this
在类中直接定义一个函数,并且将这个函数绑定到元素的onClick事件上,当前这个函数的this指向的是谁呢?
◼ 默认情况下是undefined
类里面的方法都是开启了严格模式的,babel默认启用严格模式
很奇怪,居然是undefined;
因为在正常的DOM操作中,监听点击,监听函数中的this其实是节点对象(比如说是button对象);
这次因为React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质React的Element对象;
那么在这里发生监听的时候,react在执行函数时并没有绑定this,默认情况下就是一个undefined;
◼ 我们在绑定的函数中,可能想要使用当前对象,比如执行 this.setState 函数,就必须拿到当前对象的this
我们就需要在传入函数时,给这个函数直接绑定this
类似于下面的写法: <button onClick={this.changeText.bind(this)}>改变文本</button>
或者
React-JSX语法
认识JSX
◼ 这段element变量的声明右侧赋值的标签语法是什么呢?
它不是一段字符串(因为没有使用引号包裹);
它看起来是一段HTML元素,但是我们能在js中直接给一个变量赋值html吗?
其实是不可以的,如果我们将 type="text/babel" 去除掉,那么就会出现语法错误;
它到底是什么呢?其实它是一段jsx的语法;
◼ JSX是什么?
JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;
它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind)
为什么React选择了JSX
·◼ React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
比如UI需要绑定事件(button、a原生等等);
比如UI中需要展示数据状态;
比如在某些状态发生改变时,又需要改变UI;
◼ 他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component);
当然,后面我们还是会继续学习更多组件相关的东西;
◼ 在这里,我们只需要知道,JSX其实是嵌入到JavaScript中的一种结构语法;
◼ JSX的书写规范:
JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者使用后面我们学习的Fragment);
为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
JSX中的标签可以是单标签,也可以是双标签;
✓ 注意:如果是单标签,必须以/>结尾;
JSX的使用
◼ jsx中的注释 {/*我是jsx的注解*/}
◼ JSX嵌入变量作为子元素
{/*Number/String/Array直接显示出来*/}
<h2>{movies}</h2>
{/*undefined/null/Boolean显示不出来*/}
<h2>{aaa+''}</h2>
{/*对象不能作为子元素显示*/}
{/*<h1>{find}</h1>*/}
情况一:当变量是Number、String、Array类型时,可以直接显示
情况二:当变量是null、undefined、Boolean类型时,内容为空;
✓ 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
✓ 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
情况三:Object对象类型不能作为子元素(not valid as a React child)
◼ JSX嵌入表达式
运算表达式
{/*运算表达式*/}
<h2>{20+30}</h2>
三元运算符
{/*三元运算符*/}
<h2>{age>18?'成年人':'未成年'}</h2>
执行一个函数
◼ jsx绑定属性
比如元素都会有title属性
<h3 title={title}>属性绑定</h3>
比如img元素会有src属性
比如a元素会有href属性
比如元素可能需要绑定class(建议使用className)
{/*绑定class属性*/}
<div class="aa ccc">我是一个class</div>
<div className="aa ccc">我是一个class</div>
比如原生使用内联样式style
{/*绑定style属性*/}
<h3 style={{color:"red",fontsize:"30px"}}>style</h3>
React事件绑定
◼ 如果原生DOM原生有一个监听事件,我们可以如何操作呢?
方式一:获取DOM原生,添加监听事件;
方式二:在HTML原生中,直接绑定onclick;
◼ 在React中是如何操作呢?我们来实现一下React中的事件监听,这里主要有两点不同
React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;
this的绑定问题
◼ 在事件执行后,我们可能需要获取当前类的对象中相关的属性,这个时候需要用到this
如果我们这里直接打印this,也会发现它是一个undefined
◼ 为什么是undefined呢?
原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;
而它内部调用时,并不知道要如何绑定正确的this;
◼ 如何解决this的问题呢?
方案一:bind给btnClick显示绑定this
方案二:使用 ES6 class fields 语法
方案三:事件监听时传入箭头函数(个人推荐)
在js当中使用redux
npm install redux
1.通过const { createStore } = require("redux") 导入 createStore
2.初始化数据
const initialState = {
name: 'cui'
}
3.定义renducer函数
4.通过 const store = createStore(renducer) 创建store
const { createStore } = require("redux")
//初始化数据
const initialState = {
name: 'cui'
}
//定义renducer函数:纯函数:
/*此函数在相同的输入值时,需产生相同的输出。
函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I / O设备产生的外部输出无关。
该函数不能有语义上可观察的函数副作用,诸如:“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。 */
function renducer() {
return initialState
}
//创建的store,自动调用renducer函数,并传入初始状态initialState作为参数
const store = createStore(renducer)
module.exports = store
使用store的数据
//导入store
const store = require("../src/store")
// 通过store.getStore()方法获取state数据
console.log(store.getState())
修改store当中的数据
index.js
const { createStore } = require("redux")
//初始化数据
const initialState = {
name: 'cui',
counter: 100
}
//定义renducer函数:纯函数:
/*此函数在相同的输入值时,需产生相同的输出。
函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I / O设备产生的外部输出无关。
该函数不能有语义上可观察的函数副作用,诸如:“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。 */
// renducer函数接收2个参数
//参数一:state之前的状态
//参数二:action触发的动作
//返回值作为store的新state
function renducer(state = initialState, action) {
console.log(state, action)
//如果数据有修改,那么返回一个 state
if (action.type === "change_name") {
return { ...state, name: action.name }
}
return initialState
}
//创建的store,自动调用renducer函数,并传入初始状态initialState作为参数
const store = createStore(renducer)
module.exports = store
修改文件
const store = require("./store")
//修改store中的数据,必须action
const nameActions = { type: "change_name", name: "chen" }
store.dispatch(nameActions)
console.log(store.getState())//{ name: 'chen', counter: 100 }
订阅store当中数据的变化(订阅文件)subscribe
const store = require("./store")
//subscribe 订阅store中的数据,当store中的数据发生改变的时候,会执行subscribe中的函数
const unsubscribe = store.subscribe(() => {
console.log("store中的数据发生改变",store.getState())
})
//修改store中的数据,必须action
store.dispatch({ type: "change_name", name: "chen" })
store.dispatch({ type: "change_name", name: "chen" })
unsubscribe() //取消订阅
store.dispatch({ type: "change_counter", counter: 200 })
简化index.js代码
const { createStore } = require("redux")
//初始化数据
const initialState = {
name: 'cui',
counter: 100
}
//定义renducer函数:纯函数:
/*此函数在相同的输入值时,需产生相同的输出。
函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I / O设备产生的外部输出无关。
该函数不能有语义上可观察的函数副作用,诸如:“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。 */
// renducer函数接收2个参数
//参数一:state之前的状态
//参数二:action触发的动作
//返回值作为store的新state
function renducer(state = initialState, action) {
switch (action.type) {
case "change_name":
return { ...state, name: action.name }
case "change_counter": return { ...state, counter: action.counter + action.counter }
default:
return state
}
}
//创建的store,自动调用renducer函数,并传入初始状态initialState作为参数
const store = createStore(renducer)
module.exports = store
动态生成action
const store = require("./store")
//subscribe 订阅store中的数据,当store中的数据发生改变的时候,会执行subscribe中的函数
const unsubscribe = store.subscribe(() => {
console.log("store中的数据发生改变", store.getState())
})
const changeNameAction = (name) => ({
type: "change_name",
name
})
const changeCounterAction = (counter) => ({
type: "change_counter",
counter
})
//修改store中的数据,必须action
store.dispatch(changeNameAction("chen"))
store.dispatch(changeNameAction("zhi"))
unsubscribe() //取消订阅
store.dispatch(changeCounterAction(12))
优化代码分成4个文件
1.actionCreators.js
const changeNameAction = (name) => ({
type: "change_name",
name
})
const changeCounterAction = (counter) => ({
type: "change_counter",
counter
})
module.exports = {
changeNameAction,
changeCounterAction
}
2.constants.js
const CHANGE_NAME = "change_name";
const CHANGE_CIUNTER = "change_counter";
module.exports = {
CHANGE_NAME,
CHANGE_CIUNTER,
};
3.index.js
const { createStore } = require("redux");
const renducer = require("./reducer");
//创建的store,自动调用renducer函数,并传入初始状态initialState作为参数
const store = createStore(renducer);
module.exports = store;
4.reducer.js
const { CHANGE_NAME, CHANGE_CIUNTER } = require("./constants");
//初始化数据
const initialState = {
name: "cui",
counter: 100,
};
//定义renducer函数:纯函数:
/*此函数在相同的输入值时,需产生相同的输出。
函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I / O设备产生的外部输出无关。
该函数不能有语义上可观察的函数副作用,诸如:“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。 */
// renducer函数接收2个参数
//参数一:state之前的状态
//参数二:action触发的动作
//返回值作为store的新state
function renducer(state = initialState, action) {
switch (action.type) {
case CHANGE_NAME:
return { ...state, name: action.name };
case CHANGE_CIUNTER:
return { ...state, counter: action.counter + action.counter };
default:
return state;
}
}
module.exports = renducer
注意:reducer只能创建一个Store,唯一修改state的方法是触发action,使用纯函数reducer修改执行修改,将state更action联系在一起,返回一个新的state
三大原则(redux)
- 单一数据源
- State是只读的
- 使用reducer纯函数执行修改