HTML 상태를 보라

33763 단어 a11yreduxhtmlstate
간단한 예를 들어 보겠습니다. 내부에 간단한 확인란이 있는 몇 개의 탭이 있습니다. 그러나 모든 단순함에 관계없이 해야 하는 것보다 조금 더 복잡하게 만든 다음 접근 방식을 최적화하여 매 단계마다 더 나아지도록 합시다.
Redux , setState , useState 를 시도해 보겠습니다. 그리고... 끝은... 예상하지 못했습니다.

TL;DR https://codesandbox.io/s/romantic-http-h6cgj





우리의 App가 다음과 같다고 가정합시다.

function App() {
  return (
    <div className="App">
      <TabHeader>
        <Tab id={1}>One</Tab>
        <Tab id={2}>Two</Tab>
        <Tab id={3}>Three</Tab>
      </TabHeader>
      <TabContext>
        <Tab id={1}>
          Content of tab1 <CheckBox />
        </Tab>
        <Tab id={2}>
          Content of tab2 <CheckBox />
        </Tab>
        <Tab id={3}>
          Content of tab3 <CheckBox />
        </Tab>
      </TabContext>
    </div>
  );
}

나는 첫 번째 예를 가능한 한 나쁘게 만들고 싶지만, 나는 이것으로 충분할 것이라고 생각합니다 ...

리덕스



오 Redux, 국가의 국가! 당신은 진실의 단일 소스를 지배하고 우리 모두를 위해 상품을 발송합니다.

물론 Redux가 항상 올바른 결정이라면 Redux를 사용하여 Tab 구성 요소를 처리하는 것은 올바른 결정입니다.

// TabHeader just renders everything inside
const TabHeader = ({ children }) => <section>{children}</section>;

// "Stateless/Dumb" Tab component
const TabImplementation = ({ children, onSelect }) => <div onClick={onSelect}>{children}</div>;

// All the logic is in TabContext
const TabContextImplementation = ({ children, selectedTab }) => (
 <section>
 {React.Children.map(children, (child) => (
   // displaying only "selected" child
   selectedTab === child.props.id  
     ? child
     : null
  ))}
  </section>
)

// connecting Tab, providing a `onSelect` action
const Tab = connect(null, (dispatch, ownProps) => ({
  onSelect: () => dispatch(selectTab(ownProps.id))
}))(TabImplementation);

// connecting TabContext, reading the `selectedTab` from the state
const TabContext = connect(state => ({
  selectedTab: state.selectedTab
}))(TabContextImplementation);

이것은 실제로 Redux가 구성 요소를 연결하는 방법과 그것이 얼마나 멋진지 아주 간단한 예입니다.

하지만, 아마도 우리는 그러한 간단한 예를 위해 그렇게 복잡한 코드가 필요하지 않을 것입니다. 최적화하자!

구성 요소 상태



자체 구성 요소 상태를 사용하려면 redux에 대한 바인딩을 제거하고 다시 연결App하여 Stateful 구성 요소로 만들기만 하면 됩니다.

class App extends React.Component {
  state = {
    selectedTab: 1
  };

  onSelect = (selectedTab) => this.setState({selectedTab});

  render () {
  return (
    <div className="App">
      <TabHeader>
        <Tab id={1} onSelect={() => this.onSelect(1)}>One</Tab>
        <Tab id={2} onSelect={() => this.onSelect(1)}>Two</Tab>
        <Tab id={3} onSelect={() => this.onSelect(1)}>Three</Tab>
      </TabHeader>
      <TabContent selectedTab={this.state.selectedTab}>
        <Tab id={1}>
          Content of tab1 <CheckBox />
        </Tab>
        <Tab id={2}>
          Content of tab2 <CheckBox />
        </Tab>
        <Tab id={3}>
          Content of tab3 <CheckBox />
        </Tab>
      </TabContent>
    </div>
  );
 }
}

그것이 우리가 필요로 하는 모든 상태 관리입니다. 더 간단할 수 있습니까?

후크


hooks가 더 좋아질까요? 예! 그들은 할 것입니다! 차이가 미미하더라도 가독성의 관점에서 보면 큰 차이입니다.

const App = () => {
  const [selectedTab, setSelected] = useState(1);
  return (
    <div className="App">
      <TabHeader>
        <Tab id={1} onSelect={useCallback(() => setSelected(1))}>One</Tab>
        <Tab id={2} onSelect={useCallback(() => setSelected(1))}>Two</Tab>
        <Tab id={3} onSelect={useCallback(() => setSelected(1))}>Three</Tab>
      </TabHeader>
      <TabContent selectedTab={selectedTab}>
        <Tab id={1}>
          Content of tab1 <CheckBox />
        </Tab>
        <Tab id={2}>
          Content of tab2 <CheckBox />
        </Tab>
        <Tab id={3}>
          Content of tab3 <CheckBox />
        </Tab>
      </TabContent>
    </div>
  );
}

우리는 해냈다?



첫 번째 예와 마지막 예의 차이점은 ... 다릅니다. 필요한 코드의 양은 거의 동일하고 이점은 비교할 수 없으며 가독성은 어떤 경우에도 완벽합니다.

유일한 큰 차이점은 "구성 요소화"에 있습니다. 로컬 상태와 후크 탭State은 로컬이고 Redux는 전역입니다. 나쁘지도 좋지도 않으며 둘 다 당신의 필요를 충족시킬 것입니다.

우리가 뭔가를 잊었습니까?



반응 작업은 훌륭했지만 html 작업은 형편없었습니다. onClick 핸들러를 div에 연결하는 것은 최악의 아이디어입니다. 절대 액세스할 수 없습니다.

Is there a way to handle a "state", and make application "accessible" in the same time?



HTML 상태



먼저 코드를 보여주고 어떻게 작동하는지 설명하겠습니다.

const TabHeader = ({ children, group }) => (
  <>
    {React.Children.map(children, (child, index) => (
      // we will store "state" in a radio-button
      <input
        class="hidden-input tab-control-input"
        defaultChecked={index === 0}
        name={group}
        type="radio"
        id={`control-${child.props.controls}`}
      />
    ))}
    <nav>{children}</nav>
  </>
);

const Tab = ({ children, controls }) => (
  // Tabs are controlled not via `div`, `button` or `a`
  // Tabs are controlled via `LABEL` attached to input, and to the tab itself
  <label htmlFor={`control-${controls}`} aria-controls={controls}>
    {children}
  </label>
);

const TabContent = ({ children, group }) => (
  <section className="tabs">
    {React.Children.map(children, (child, index) => (
      <section class="tab-section" id={child.props.id}>{child.props.children}</section>
    ))}
  </section>
);

const CheckBox = () => <input type="checkbox" />;

const App = () => (
  <div className="App">
    <TabHeader group="tabs">
      <Tab controls="tab1">One</Tab>
      <Tab controls="tab2">Two</Tab>
      <Tab controls="tab3">Three</Tab>
    </TabHeader>
    <TabContent>
      <Tab id="tab1">
        Content of tab1 <CheckBox />
      </Tab>
      <Tab id="tab2">
        Content of tab2 <CheckBox />
      </Tab>
      <Tab id="tab3">
        Content of tab3 <CheckBox />
      </Tab>
    </TabContent>
  </div>
);

그게 다야, 상태 관리가 전혀 없으며 이전보다 훨씬 잘 작동합니다. redio-buttonslabelstabs , 그들과 관련이 없습니다 ... 어떻게 작동하는지 잘 모르겠지만 작동합니다.

Here is the proof - link to the sandbox - https://codesandbox.io/s/romantic-http-h6cgj



좀 더 구체적으로 말하자면 reach-ui tabs처럼 잘 작동합니다. 시도해 보세요. 동일합니다. Reach가 열렬히 열망하는 진정한 접근성 경험입니다.
그러나 reach-ui는 탭이 제대로 작동하도록 키보드 이벤트와 포커스 관리를 처리해야 합니다.

당신은 믿지 않을 수 있습니다 - 그것은 정확히 동일하게 작동하는 내 예입니다.

비밀



비밀은 CSS에 있습니다. 모든active은 시각적으로 숨겨져 있습니다. 그래서 존재하고 초점을 맞출 수 있지만 보이지 않습니다. 그리고 다른 모든 것은 Left 상태를 사용합니다.

// input 1 is checked? Then tab 1 should be visible.
.tab-control-input:checked:nth-of-type(1) ~ .tabs .tab-section:nth-of-type(1){
  display: block;
}

// and label should be bold if corresponding radio button is active
.tab-control-input:checked:nth-of-type(1) ~ nav label:nth-of-type(1){
  font-weight: 600;
}

// and focus ring should be teleported to a label
.tab-control-input:focus:nth-of-type(1) ~ nav label:nth-of-type(1) {
  outline: thin dotted;
  outline: 5px auto -webkit-focus-ring-color;
}

구조에 제한이 있습니다. Right 연산자는 input가 한 쪽에 있어야 하고 input + ~가 다른 쪽에 있어야 합니다. 즉, 모든 입력은 왼쪽에 있고 모든 탭은 오른쪽에 있습니다. 결과적으로 이와 같은 것이

.tab-control-input:checked ~ .tabs .tab-section {
  display: block;
}

모두 조건과 일치하는 한 선택된 input 항목에 대해 모두 label를 표시합니다.

이 예에서는 tab 를 사용했지만 .tabs (theurge 의 코드 스니펫) 또는 input와 같은 더 고유한 선택기로 CSS를 즉석에서 생성하는 것이 좋습니다(CSS-in-JS). 리치에서처럼.

결론



그래서, 서프라이즈! HTML은 상태 관리가 될 수 있으며 상자에서 더 의미 있고 접근하기 쉬운 결과를 얻을 수 있습니다. Play with it 잠시 :)

알다시피 - 플랫폼을 사용하십시오 :) 뭔가를 놓칠 수도 있습니다.

좋은 웹페이지 즐겨찾기