0

Axii

 3 years ago
source link: https://axii.js.org/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
AxiiBasic UsageReactive DataDynamic StructureCreate Better ComponentLayout StyleNon-Layout StyleState & CallbackExtending & Overwriting ComponentQuick StartCreate ApplicationComponent & Icon LibraryAxii1.0.8中文

Axii is a javascript UI framework. The main features are:

  • Using function and jsx to create component, not template.
  • Using vue 3 like reactive data as state。Only update related element when data changed, not re-rendering component.
  • It provides extending and overwriting mechanism for component to improve efficiency for both component developer and user.

The following contents will give you a tutorial to Axii basic usage. More documents is working in progress, help will be appreciated. Thanks to creators of React and Vue.

Using Reactive Data

  • Use atom to make non-object data like string/number/boolean reactive. Object which do not need deep watch can use it too.
  • use .value to get and set value of data created by using atom. Don't use .value to bind data to element property.
  • Use computed to create computed value.
Example
Hello, please enter your name.
/** @jsx createElement */
import { createElement, atom, computed } from 'axii'

export default function Basic() {
  const firstName = atom('')
  const lastName = atom('')
  const fullName = computed(() => {
    if (!firstName.value && !lastName.value) return 'please enter your name'
    return `${firstName.value}-${lastName.value}`
  })

  const setFirstName = (e) => {
    firstName.value = e.target.value
  }

  const setLastName = (e) => {
    lastName.value = e.target.value
  }

  return (
    <div>
      <input value={firstName} placeholder="firstName" onInput={setFirstName}/>
      <input value={lastName} placeholder="lastName" onInput={setLastName}/>
      <div>Hello, {fullName}.</div>
    </div>
  )
}
Axii only update element which use a reactive data as children or property, which means component only render once if the references of component props is not changed.

Dynamic Structure

Use function to represent a dynamic structure such as repeated structure or conditional structure. (Axii will auto detect dynamic structure expression in jsx in the future, so user do not need to use function to declare.)

Example
/** @jsx createElement */
import {createElement, atom, reactive} from 'axii'
export default function Code() {
  const inputItem = atom('')
  const items = reactive([])

  const setInputItem = (e) => inputItem.value = e.target.value
  const addItem = () => {
    items.push(inputItem.value)
    inputItem.value = ''
  }

  const removeItem = (index) => {
    items.splice(index, 1)
  }

  return (
    <div>
      <input value={inputItem} placeholder="plan something" onInput={setInputItem}/>
      <button onClick={addItem}>add</button>
      <ul>
        {function List() {
          return items.map((item, i) => (
            <li>
              <span>{item}</span>
              <button onClick={() => removeItem(i)}>x</button>
            </li>
          ))
        }}
      </ul>
    </div>
  )
}

Create Better Component

Axii provides an API called createComponent to help user create better component with:

  • Better style system
    • Allows separating style related logic from structure logic.
    • Allows separating layout and non-layout style.
  • More powerful component behavior control.
    • Auto revoke callback property.
    • Auto implement controlled/uncontrolled/delegate mode.
  • Feature based component logic separation.

Layout Style

Layout Style refers to position or size related style, such as display/width/height.

Axii introduces a special attribute type called Layout Attribute to help user deal with layout style. Here are the guidelines of how to use Layout Attribute:

  1. Use semantic tag name instead of div or span.
  2. Use layout attribute text/inline/block to declare layout type. Axii will apply display: inline to text type and display: inline-block to inline type.
  3. Use flex-display or extend display by yourself to handle complex layout(Docs upcoming).
  4. Use box related attribute like width/heigth with layout type as prefix.
You can use '-' to concat key and value. The following usage are equal:
flex-justify-content="space-between"
flex-justify-content-space-between
Use boolean value(can be reactive data) to control attribute dynamically:
flex-justify-content-space-between=true
Exampleswimmingdeleteswimmingdelete
import { createElement } from 'axii'
export default function Code() {
  return (
    <todoList block block-width="100%">
      <todoItem block flex-display flex-justify-content="space-between">
        <name>swimming</name>
        <action>delete</action>
      </todoItem>
      <todoItem block flex-display flex-justify-content-space-between>
        <name>swimming</name>
        <action>delete</action>
      </todoItem>
    </todoList>
  )
}

Pass Layout Attribute To Component

Layout attribute can be passed to Component to control component box style. Simply add layout: namespace to attribute.

Example
import { createElement } from 'axii'

function Box() {
  return <div style={{background: '#cecece'}}>box</div>
}

export default function Code() {
  return (
    <container>
      <line block>
        <Box layout:inline/>
      </line>
      <line block block-margin-top-10px>
        <Box layout:inline layout:inline-width-300px/>
      </line>
      <line block block-margin-top-10px>
        <Box layout:block/>
      </line>
    </container>
  )
}

Non-Layout Style

Non-Layout style refer to layout irrelated style such as color/font-family. User can write better non-layout style by implementing feature.

Examplea link
/** @jsx createElement */
import { createElement, atom, createComponent, propTypes } from 'axii'

function Component({ onFocus, onBlur}) {
  return <container>
    <line block>
      <box use="input" onFocus={onFocus} onBlur={onBlur}/>
    </line>
    <line block>
      <textLink use="a" href="#">a link</textLink>
    </line>
  </container>
}

Component.Style = (fragments) => {
  fragments.root.elements.box.style(({focused}) => {
    return {
      borderStyle: 'solid',
      borderWidth: 1,
      outline: 'none',
      borderColor: focused.value ? 'blue': 'black'
    }
  })

  // pseudo class
  fragments.root.elements.textLink.match.hover.style({
    color: 'green'
  })
}

Component.Style.propTypes = {
  focused: propTypes.bool.default(() => atom(false)),
  onFocus: propTypes.callback.default(() => ({focused}) => focused.value = true),
  onBlur: propTypes.callback.default(() => ({focused}) => focused.value = false)
}

export default createComponent(Component)
Checkout more about feature mechanism bellow.

Component Controlling

Component created by using createComponent is enhanced with:

  • Auto state manage modes support:
  • controlled mode
  • uncontrolled mode
  • delegated mode
  • Component default mutation prevention support

Component event callback declared in proptypes is more powerful than simple function property. It allows you:

  • Receiving 3 extra arguments:
    • draftProps: the data you can mutate.
    • props: the origin props object, do not mutate it.
    • event: the event object.
  • Preventing default event callback mutation by return false.
  • Completely overwriting component callback by mark callback with overwrite.

Component state managing have three modes differed by passing different type of data, See details below:

Base Example Component

Example
/** @jsx createElement */
import { createElement, atom, propTypes } from 'axii'

export default function Input({ value, onChange}) {
  return <input value={value} onInput={onChange}/>
}

Input.propTypes = {
  value: propTypes.string.default(() => atom('')),
  onChange: propTypes.callback.default(() => ({value}, props, e) => {
    value.value = e.target.value
  })
}

Controlled Mode

Passing in non-reactive data makes component fully controlled by upper scope. Component default event callback will not mutate state.

Example
/** @jsx createElement */
import { createElement } from 'axii'
import Input from './Input.jsx'

export default function ControlledComponent() {
  const value = 'controlled value'
  return <Input value={value}/>
}

Uncontrolled Mode

If no data passed in, component will create and mutate state according to proptypes.

Example
/** @jsx createElement */
import { createElement } from 'axii'
import Input from './Input.jsx'


export default function InputDemo() {
  const onChange = () => {

  }
  return <Input onChange={onChange}/>
}

Delegated Mode

If pass in reactive data, component will mutate it with event callback defined in proptypes.

Example
value:
/** @jsx createElement */
import { createElement, atom } from 'axii'
import Input from './Input.jsx'

export default function InputDemo() {
  const value = atom('')
  return (
    <container>
      <Input value={value}/>
      <div>{() => `value: ${value.value}`}</div>
    </container>
  )

}

Extending & Overwriting Component

Axii feature mechanism help you split component code by features, not structure. A feature can:

  • Define its own state、event and style。
  • Mutate component structure.
  • Attach event callback to any element.
Example
/** @jsx createElement */
import { createElement, createComponent, atom, propTypes } from 'axii'

function Input({ value, onChange}) {
  return <input value={value} onInput={onChange}/>
}

Input.propTypes = {
  value: propTypes.string.default(() => atom('')),
  onChange: propTypes.callback.default(() => ({value}, props, e) => {
    value.value = e.target.value
  })
}

function InputStyle(fragments) {
  fragments.root.elements.input.onFocus((e, {onFocus}) => {
    onFocus()
  })
  fragments.root.elements.input.onBlur((e, {onBlur}) => {
    onBlur()
  })
  fragments.root.elements.input.style(({focused}) => {
    return {
      borderStyle: 'solid',
      borderWidth: 1,
      outline: 'none',
      borderColor: focused.value ? 'blue': 'black'
    }
  })
}

InputStyle.propTypes = {
  focused: propTypes.string.default(() => atom(false)),
  onFocus: propTypes.callback.default(() => ({focused}) => focused.value = true),
  onBlur: propTypes.callback.default(() => ({focused}) => focused.value = false),
}

export const InputWithStyle = createComponent(Input, [InputStyle])

export default function InputDemo() {
  return <InputWithStyle />
}

User can use Component.extend to extend component by self, no extra support of developer needed.

Example
/** @jsx createElement */
import { createElement } from 'axii'
import { InputWithStyle } from "./FeatureBasedInput.jsx";

const InputWithCustomStyle = InputWithStyle.extend(function CustomStyle(fragments){
  fragments.root.elements.input.style(({focused}) => ({
    borderColor: focused.value ? 'red' : 'black'
  }))
})

export default function InputDemo() {
  return <InputWithCustomStyle />
}

For real-world example of feature mechanism, you can checkout Table component.

Quick Start

Creating App

npx degit ariesate/engine/packages/vite-axii my-project
cd my-project

npm install
npm run dev

Component Library & Icon Library

npm install axii-components --save
npm install axii-icons --save

Visit Component Repo

Visit Component Site


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK