公司实习的地方,使用react,然后打算学习一下react,并把基础知识点整理一下,毕业设计的前端,也使用react来搭建前端框架。
这里主要有如下react基础知识点:
先使用react写一个helloworld,体验一下。
先导入react开发版本js,有三个,react.development.js、react-dom.development.js、babel.min.js
react.js:React 的核心库
react-dom.js:提供操作DOM 的react 扩展库
babel.js:为babel库,两个作用1、将es6语法转化为es5,2、解析JSX语法代码为纯JS 语法代码的库(这里使用的是后者语法)
<body>
<div id="example">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel"> // 声明:告诉babel.js这里有jsx语法
// 1、创建虚拟DOM元素对象
var vDom = <h1>Hello React</h1> // 不是字符串 jsx 语法
// 2、将虚拟DOM渲染到页面真实DOM容器中
ReactDOM.render(vDom, document.getElementById("example"))
script>
body>
页面:
步骤(重点学习的地方):
在< script type=“text/babel”> 中编写:声明:告诉babel.js这里有jsx语法
2. 创建虚拟DOM元素对象:就是定义一个变量,将HTML标签赋值于变量
3. 将虚拟DOM渲染到页面真实DOM容器中
技术点:
var vDom = Hello React
ReactDOM.render(vDom, document.getElementById("example"))
功能: 在页面中遍历一个数组,将其显示在页面中。
问题: 如何将一个数据的数组 转换为 一个标签的数组 很常用,使用数组的map() 方法 和 箭头函数
<div id="example1">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
/**
* 功能:动态展示列表数据
* 问题:如何将一个数据的数组 转换为 一个标签的数组 很常用
* 使用数组的map() 方法 和 箭头函数
* */
const names = ['jQuery', 'zepto', 'angular', 'react', 'vue']
// 1、创建虚拟DOM
// li 标签一定要带 key属性,而且还不能一样 否则警告
const ul = (
<ul>
{
names.map(name=> <li key={
name}>{
name}</li>)}
</ul>
)
// 2、渲染虚拟DOM
ReactDOM.render(ul, document.getElementById('example'))
script>
技术点:
3. {} 大括号写法:
4. 数组方法map():js方法,是对数组的遍历,里面为回调函数,方法形式:
map(value)
:value 为数组的值,
map(value, index)
:value 为数组的值,index为数组下标
<body>
<div id="test01">div>
<div id="test02">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/javascript">
/**
* 需求,两种方式实现虚拟DOM的创建
* 1、使用原生js方法:const vDom1 = React.createElement('h1', {id:'myTitle'},'hello')
* 2、使用jsx方法:const vDom2 = {msg.toLowerCase()}
*/
const msg = 'I Like you!'
const myId = 'fengfanli'
// 1、创建虚拟DOM元素对象
// const element = React.createElement('h1', {id:'myTitle'},'hello')
const vDom1 = React.createElement('h2', {
id: myId.toLowerCase()}, msg.toUpperCase())
// debugger
// 2、将虚拟DOM渲染到页面真实DOM容器中
ReactDOM.render(vDom1, document.getElementById('test01'))
script>
<script type="text/babel">
// 1、创建虚拟DOM元素对象
const vDom2 = <h3 id={
myId.toUpperCase()}>{
msg.toLowerCase()}</h3>
// debugger
// 2、将虚拟DOM渲染到页面真实DOM容器中
ReactDOM.render(vDom2, document.getElementById('test02'))
script>
body>
当应用是以多组件的方式实现, 这个应用就是一个组件化的应用。
当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
<body>
<div id="example1">div>
<div id="example2">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
/**
* 组件定义的两种方式
* 1、使用方法定义,没有状态用此种方式(简单组件)
* 2、使用类定义,(复杂组件)
*/
// 1.定义组件
//方式1: 工厂函数组件(简单组件):没有状态的组件
function MyComponent() {
return <h2>工厂函数组件(简单组件)</h2>
}
//方式2: ES6类组件(复杂组件)
class MyComponent2 extends React.Component{
render(){
console.log(this)
return <h2>工厂函数组件(简单组件)</h2>
}
}
// 2.渲染组件方式
ReactDOM.render(<MyComponent />, document.getElementById('example1'))
ReactDOM.render(<MyComponent2 />, document.getElementById('example2'))
script>
body>
自定义组件,功能说明如下
state是组件对象最重要的属性, 值是对象(可以包含多个数据)
组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
初始化状态:
constructor (props) {
super(props)
this.state = {
stateProp1 : value1,
stateProp2 : value2
}
}
this.state.statePropertyName
// statePropertyName 自定义名称 this.setState({
stateProp1 : value1,
stateProp2 : value2
})
<body>
<div id="example">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
/**
* 需求:自定义组件,功能说明如下
* 1. 显示 h2 标题,初始文本为:你喜欢我
* 2. 点击标题更新为:我喜欢你
* */
// 1.定义组件
class Like extends React.Component{
// 2)、构造器
constructor(props){
super(props)
// 初始化状态
this.state = {
isLikeMe: false
}
// 将新增方法中的this 强制绑定为组件对象
this.handleClick = this.handleClick.bind(this)
}
// 1)、重写组件类的方法
render(){
// 读取状态
const {
isLikeMe} = this.state // 第二种方式
return <h2 onClick={
this.handleClick}>{
isLikeMe?'你喜欢我':'我喜欢你'}</h2>
}
// 3)、新添加的方法:内部的 this 默认不是组件对象,而是 undefined, 所以要在 构造器中 绑定方法
handleClick(){
console.log('handleClick()',this)
// 得到原有状态 并取反
const isLikeMe = !this.state.isLikeMe
//更新状态
this.setState({
isLikeMe})
}
}
// 2.渲染组件
ReactDOM.render(<Like />, document.getElementById('example'))
script>
body>
需求: 自定义用来显示一个人员信息的组件
1). 姓名必须指定
2). 如果性别没有指定, 默认为男
3). 如果年龄没有指定, 默认为18
理解
作用
编码操作
this.props.propertyName
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
Person.defaultProps = {
name: 'Mary'
}
constructor (props) {
super(props)
console.log(props) // 查看所有属性
}
<body>
<div id="example1">div>
<div id="example2">div>
<div id="example3">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/prop-types.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
/**
* props属性 和 prop-types.js prop-types.js: 对值进行设定
* 需求:自定义用来显示一个人员信息的组件,效果如页面,说明
* 1、如果性别没有指定,默认为男
* 2、如果年龄没有指定,默认为18
*/
// 1、定义组件
/**
* 使用 对象组件
* 组件对象有三个 state props refs
**/
class Person extends React.Component{
render(){
console.log(this)
return(
<ul>
<li>姓名:{
this.props.name}</li>
<li>年龄:{
this.props.age}</li>
<li>性别:{
this.props.sex}</li>
</ul>
)
}
}
// 指定属性 默认值
Person.defaultProps = {
age: 18,
sex: '男'
}
// 对 props 中的属性值进行类型限制和必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number
}
// 2、渲染组件
const p1 = {
name: 'Tom',
age: 18,
sex: '女'
}
ReactDOM.render(<Person name={
p1.name} age={
p1.age} sex={
p1.sex} />, document.getElementById("example1"))
const p2 = {
name: 'Jack',
}
ReactDOM.render(<Person name={
p2.name} age={
20} />, document.getElementById("example2"))
/**
* ... 的作用
* 1、打包
* function fn(...as){} fn(1, 2, 3) : 打包的意思
* 2、解包
* const arr1 = [1, 2, 3] const arr2 = [6, ...arr1, 9] :解包的意思
*
* @type {
{name: string}}
*/
const p3 = {
name: 'Feng',
}
ReactDOM.render(<Person {
...p3} />, document.getElementById("example3"))
script>
body>
新添加了一个js:prop-types,这个库是在子组件中,对父组件传给子组件的属性值进行加以设定。
ReactDOM.render( , document.getElementById("example3"))
this.props.属性名
获取值。...
:将对象的所有属性通过props传递请区别一下组件的props和state属性
需求: 自定义组件, 功能说明如下:
组件内的标签都可以定义ref属性来标识自己
a. this.msgInput = input}/>
b. 回调函数在组件初始化渲染完或卸载时自动调用
在组件中可以通过this.msgInput
来得到对应的真实DOM元素
作用: 通过ref获取组件内容特定标签对象, 进行读取其相关数据
<input onFocus={this.handleClick}/>
handleFocus(event) {
event.target //返回input对象
}
组件内置的方法中的this为组件对象
在组件类中自定义的方法中this为null
a. 强制绑定this: 通过函数对象的bind()
b. 箭头函数(ES6模块化编码时才能使用)
<body>
<div id="example1">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/prop-types.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
/**
* 事件 与 ref
* 需求:自定义组件,功能说明
* 1、界面如图所示
* 2、点击按钮,提示第一个输入框中的值
* 3、当第二个输入框失去焦点时,提示这个输入框中的值
*/
// 1、创建组件
class MyComponent extends React.Component {
constructor(props) {
super(props)
this.showInput = this.showInput.bind(this)
this.handleBlur = this.handleBlur.bind(this)
}
showInput() {
const value = this.refs.content.value;
alert(value)
alert(this.input.value)
}
// 事件都有一个固定的形参
handleBlur(event) {
console.log(event)
console.log(event.target) //指的是标签
// event.target : 指的是 input 表单框
alert(event.target.value)
}
/* 最后一个比较特别,操作的 dom 元素,是发生事件的元素。*/
render() {
return (
<div>
<input type="text" ref="content"/>
<input type="text" ref={
input => this.input = input}/>
<button onClick={
this.showInput}>提升输入</button>
<input type="text" onBlur={
this.handleBlur} placeholder="失去焦点提示内容"/>
</div>
)
}
}
// 2、渲染组件
ReactDOM.render(<MyComponent/>, document.getElementById("example1"))
script>
body>
大体步骤与前一样
ref属性
的value值为唯一值,然后在点击事件中 通过 this.refs.ref属性名
获取ref属性
的value值为回调函数{input => this.inputMsg = input}
,返回一个,然后在点击事件中 通过 this.inputMsg
获取功能: 组件化实现此功能
<body>
<div id="example">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/prop-types.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
/**
* 界面中有一个文本框和按钮,按钮中显示下面的几个数据
* 下面是一个 ul 列表,默认遍历 数组中的数据。
* 事件:点击按钮,文本框中的数据出现在下面中的 ul 列表中
*/
/**
*问题:数据保存在哪个组件内?
* 看数据是某个组件需要(给它),还是某些组件需要(给共同的父组件)
*问题2:需要在子组件中改变父组件的状态
* 子组件不能直接改变父组件的状态
* 状态在哪个组件,更新状态的行为(方法)就应该定义在哪个组件
* 解决:父组件定义函数,传递给子组件,子组件调用
*
*组件化编写功能的流程
* 1、拆分组件
* 2、实现静态组件(只有静态界面,没有动态数据和交互)
* 3、实现动态组件
* 1)实现初始化数据动态显示
* 2) 实现交互功能
*/
// 主界面
class Counter extends React.Component {
constructor(props) {
super(props)
// 初始化状态
this.state = {
todos: ['吃饭', '睡觉', '打豆豆']
}
this.addTodos = this.addTodos.bind(this)
}
// 定义一个添加的函数
addTodos(todo) {
// this.state.todos.unshift(todo) //不能这么做
const {
todos} = this.state
todos.unshift(todo)
// 更新状态
this.setState({
todos})
}
render() {
const {
todos} = this.state
return (
<div>
<h1>Simple TODO List</h1>
<Add count={
todos.length} addTodos={
this.addTodos}/>
<List todos={
todos}/>
</div>
)
}
}
// 子界面
class Add extends React.Component {
constructor(prop) {
super(prop)
this.addData = this.addData.bind(this)
}
render() {
return (
<div>
<input type="text" ref={
input => this.todoInput = input}/>
<button onClick={
this.addData}>add # {
this.props.count + 1} </button>
</div>
)
}
addData() {
// 1、读取输入的数据
const data = this.todoInput.value.trim();
// 2、检查合法性
if (!data) {
return
}
// 3、添加
console.log(data)
this.props.addTodos(data)
// 4、清除输入
this.todoInput.value = ''
}
}
// 数据校验,放在上面出错
Add.propTypes = {
count: PropTypes.number.isRequired,
addTodos: PropTypes.func.isRequired
}
// 子界面
class List extends React.Component {
render() {
const {
todos} = this.props;
/* => : 箭头函数的意义,有两个:函数、return, 这里如果加 {}:方法体 的话,里面一定要有 return*/
/*todos.map((value, index)=>{value} )*/
return (
<div>
<ul>
{
todos.map((value, index) => {
return <li key={
index}>{
value}</li>
})}
</ul>
</div>
)
}
}
List.propTypes = {
todos: PropTypes.array.isRequired
}
ReactDOM.render(<Counter/>, document.getElementById('example'))
script>
body>
this.props
进行接收。this.props.todos
获取数据,然后在render()方法中进行遍历渲染出来。在Google中安装扩展软件,安装React扩展工具。可以查看自己定义的组件,以及三大属性,其实只能看到state和props,这两个是在组件上,ref是在标签上,这个只能在元素中查看。
看截图:
Counter类组件
Add类组件
拆分组件: 拆分界面,抽取组件
实现静态组件: 使用组件实现静态页面效果
实现动态组件
a. 动态显示初始化数据
b. 交互功能(从绑定事件监听开始)
需求: 自定义包含表单的组件
问题: 在react应用中, 如何收集表单输入数据
包含表单的组件分类
a. 受控组件: 表单项输入数据能自动收集成状态
b. 非受控组件: 需要时才手动读取表单输入框中的数据
<body>
<div id="example">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/prop-types.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/babel">
/**
* 需求: 自定义包含表单的组件
* 1、界面如下所示
* 2、输入用户名密码后,点击登陆提示输入信息
* 3、不提交表单 event.preventDefault()
*/
class LoginForm extends React.Component {
constructor(prop) {
super(prop)
this.state = {
pwd: ''
}
this.handleLogin = this.handleLogin.bind(this)
this.handleChange = this.handleChange.bind(this)
}
render() {
return (
/**
* 用户名 为非受控组件
* 密码 为受控组件
*/
<form action="/test" onSubmit={
this.handleLogin}>
用户名:<input type="text" ref={
input => this.username = input}/>
密码: <input type="password" value={
this.state.pwd} ref={
input => this.password = input}
onChange={
this.handleChange}/>
<input type="submit" value="登陆"/>
</form>
)
}
handleLogin(event) {
const uname = this.username.value // 操作DOM
const {
pwd} = this.state // 操作react 属性
alert(`准备提交的用户名为${
uname}, 密码${
pwd}`)
// 阻止事件的默认行为(提交)
event.preventDefault()
}
handleChange(event) {
// 读取输入框的值
const password = event.target.value
// 更新pwd的状态
this.setState({
pwd: password})
}
}
ReactDOM.render(<LoginForm/>, document.getElementById('example'))
script>
body>
步骤与前一致
需求: 自定义组件
class HelloWorld extends React.Component {
constructor(props) {
super(props)
this.state = {
date: new Date()
}
}
componentDidMount () {
setInterval(() => {
this.setState({
date: new Date()
})
}, 1000)
}
render () {
console.log('render()')
return (
<p>
Hello, <input type="text" placeholder="Your name here"/>!
It is {this.state.date.toTimeString()}
p>
)
}
}
ReactDOM.render(
<HelloWorld/>,
document.getElementById('example')
)
xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
a. 包含了所有需要的配置
b. 指定好了所有的依赖
c. 可以直接安装/编译/运行一个简单效果
react提供了一个用于创建react项目的脚手架库: create-react-app
项目的整体技术架构为: react + webpack + es6 + eslint
使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
npm install -g create-react-app
create-react-app hello-react
cd hello-react
npm start
ReactNews
|--node_modules---第三方依赖模块文件夹
|--public
|-- index.html-----------------主页面
|--scripts
|-- build.js-------------------build打包引用配置
|-- start.js-------------------start运行引用配置
|--src------------源码文件夹
|--components-----------------react组件
|--index.js-------------------应用入口js
|--.gitignore------git版本管制忽略的配置
|--package.json----应用包配置文件
|--README.md-------应用描述说明的readme文件
实现静态组件
实现动态组件
代码上库啦。
jQuery: 比较重, 如果需要另外引入不建议使用
axios: 轻量级, 建议使用
a. 封装XmlHttpRequest对象的ajax
b. promise风格
c. 可以用在浏览器端和node服务器端
fetch: 原生函数, 但老版本浏览器不支持
a. 不再使用XmlHttpRequest对象提交ajax请求
b. 为了兼容低版本的浏览器, 可以引入兼容库fetch.js
需求:
<body>
<div id="example">div>
<script type="text/javascript" src="../js/react.development.js">script>
<script type="text/javascript" src="../js/react-dom.development.js">script>
<script type="text/javascript" src="../js/prop-types.js">script>
<script type="text/javascript" src="../js/babel.min.js">script>
<script type="text/javascript" src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.js">script>
<script type="text/babel">
/**
* 需求:
* 1、界面效果如下,
* 2、根据指定的关键字在GitHub上搜索匹配的最受关注的库
* 3、显示库名,点击链接查看库
* 4、测试接口:https://api.github.com/search/repositories?q=r&sort=stars
*/
class MostStarRepo extends React.Component {
state = {
repoName: '',
repoUrl: ''
}
// 最后发请求
componentDidMount() {
// 使用 axios 发送 异步的Ajax 请求 re:可以改变可以传 sort=stars:排序模式
const url = 'https://api.github.com/search/repositories?q=re&sort=stars'
axios.get(url)
.then(response => {
const result = response.data
console.log(response);
// 得到数据
const {
name, html_url} = result.items[0]
// 更新状态
this.setState({
repoName: name,
repoUrl: html_url
})
})
// 错误信息
.catch(error => {
alert(error.message)
})
// 使用 fetch 发送异步的 Ajax 请求
/*fetch(url)
.then(response => {
return response.json()
})
.then(data => {
// 得到数据
const {name, html_url} = data.items[0]
// 更新状态
this.setState({
repoName: name,
repoUrl: html_url
})
})*/
}
render() {
const {
repoName, repoUrl} = this.state
if (!repoName) {
return <h2>Loading</h2>
} else {
return <h2>Most star repo is <a href={
repoUrl}>{
repoName}</a></h2>
}
}
}
ReactDOM.render(<MostStarRepo/>, document.getElementById('example'))
script>
body>
https://github.com/axios/axios
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
App
List
上库啦······ 库中代码为:13_react-app
componentWillReceiveProps(nextProps)
: 监视接收到新的props, 发送ajaxfetch(url).then(function(response) {
return response.json()
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
});
fetch(url, {
method: "POST",
body: JSON.stringify(data),
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
})
后面专门讲解
什么是路由?
a. 一个路由就是一个映射关系(key:value)
b. key为路由路径, value可能是function/component
路由分类
a. 后台路由: node服务器端路由, value是function, 用来处理客户端提交的请求并返回一个响应数据
b. 前台路由: 浏览器端路由, value是component, 当请求的是路由path时, 浏览器端前没有发送http请求, 但界面会更新显示对应的组件
后台路由
a. 注册路由: router.get(path, function(req, res))
b. 当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
前端路由
a. 注册路由: < Route path="/about" component={About}>
b. 当浏览器的hash变为#about时, 当前路由组件就会变为About组件
history库
a. 网址: https://github.com/ReactTraining/history
b. 管理浏览器会话历史(history)的工具库
c. 包装的是原生BOM中window.history和window.location.hash
history API
a. History.createBrowserHistory(): 得到封装window.history的管理对象
b. History.createHashHistory(): 得到封装window.location.hash的管理对象
c. history.push(): 添加一个新的历史记录
d. history.replace(): 用一个新的历史记录替换当前的记录
e. history.goBack(): 回退到上一个历史记录
f. history.goForword(): 前进到下一个历史记录
g. history.listen(function(location){}): 监视历史记录的变化
<BrowserRouter>
<HashRouter>
<Route>
<Redirect>
<Link>
<NavLink>
<Switch>
react-router
: npm install --save react-router@4
bootstrap.css
:
原来没加router时,组件的属性有 props、state
加上route时,在Route下的组件上,有了history、location、match三个对象。
都上库啦。
代码为 14_react_router,三级路由都在这一个项目中。
官网: http://www.material-ui.com/#/
github: https://github.com/callemall/material-ui
PC官网: https://ant.design/index-cn
Github: https://github.com/ant-design/ant-design/
npm install create-react-app -g
create-react-app antm-demo
cd antm-demo
npm start
下载
npm install antd-mobile --save
import React, {Component} from 'react'
// 分别引入需要使用的组件
import Button from 'antd-mobile/lib/button'
import Toast from 'antd-mobile/lib/toast'
export default class App extends Component {
handleClick = () => {
Toast.info('提交成功', 2)
}
render() {
return (
<div>
<Button type="primary" onClick={this.handleClick}>提交Button>
div>
)
}
}
src/index.js
import React from 'react';
import ReactDOM from 'react-dom'
import App from "./App"
// 引入整体css
import 'antd-mobile/dist/antd-mobile.css'
ReactDOM.render(<App />, document.getElementById('root'))
index.html
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js">script>
<script>
if ('addEventListener' in document) {
document.addEventListener('DOMContentLoaded', function() {
FastClick.attach(document.body);
}, false);
}
if(!window.Promise) {
document.writeln('